@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
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","names":["compose"],"sources":["../../src/react/compose.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { ComponentType } from 'react'\nimport { coreUiFeature } from '../core/core-feature'\nimport { EditorCommandPalette } from './EditorCommandPalette'\nimport { aiFeature } from '../features/ai/react'\nimport { authorsFeature } from '../features/authors/react'\nimport { blockUiFeature } from '../features/block-ui/react'\nimport { collabFeature } from '../features/collab/react'\nimport { commentsFeature } from '../features/comments/react'\nimport { compositeFeature } from '../features/composites/react'\nimport { mediaFeature } from '../features/media/react'\nimport { themesFeature } from '../features/themes/react'\nimport { transcriptFeature } from '../features/transcript/react'\nimport { compositeAiFeature } from '../integrations/composite-ai'\nimport { transcriptAiFeature } from '../integrations/transcript-ai'\nimport { type ComposedEditor, type ComposeOptions, composeEditor as compose, type UiBindings } from '../presets/compose'\n\ntype ReactComponent = ComponentType<{ editor: Editor }>\n\n/** The React bindings of every UI-bound catalog feature. */\nconst REACT_BINDINGS = {\n 'core-ui': () => coreUiFeature<ReactComponent>({ palette: EditorCommandPalette }),\n 'media': mediaFeature,\n 'transcript': transcriptFeature,\n 'composites': compositeFeature,\n 'authors': authorsFeature,\n 'comments': commentsFeature,\n 'ai': aiFeature,\n 'composite-ai': compositeAiFeature,\n 'transcript-ai': transcriptAiFeature,\n 'collab': collabFeature,\n 'block-ui': blockUiFeature,\n 'themes': themesFeature,\n} satisfies UiBindings<ReactComponent>\n\n/**\n * `composeEditor` of `presets/compose` over the React bindings: the editor of the three dimensions (`content`,\n * `interaction`, `ai`), pruned by the capabilities of `options.backend`.\n */\nexport const composeEditor = (options: ComposeOptions<ReactComponent>): ComposedEditor<ReactComponent> => compose(REACT_BINDINGS, options)\n\n/**\n * The advanced editor on the React surface: `{ content: 'rich', interaction: 'full', ai: true }`, every dimension option\n * overridable (`compositeKinds`, `backend`, `add`, `remove`, ...), so the schema is the one of `advancedSchemaExtensions()`.\n */\nexport const advancedEditor = (options: Partial<ComposeOptions<ReactComponent>> = {}): ComposedEditor<ReactComponent> => composeEditor({ content: 'rich', interaction: 'full', ai: true, ...options })\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,iBAAiB;CACrB,iBAAiB,cAA8B,EAAE,SAAS,qBAAqB,CAAC;CAChF,SAAS;CACT,cAAc;CACd,cAAc;CACd,WAAW;CACX,YAAY;CACZ,MAAM;CACN,gBAAgB;CAChB,iBAAiB;CACjB,UAAU;CACV,YAAY;CACZ,UAAU;AACZ;;;;;AAMA,MAAa,iBAAiB,YAA4EA,gBAAQ,gBAAgB,OAAO;;;;;AAMzI,MAAa,kBAAkB,UAAmD,CAAC,MAAsC,cAAc;CAAE,SAAS;CAAQ,aAAa;CAAQ,IAAI;CAAM,GAAG;AAAQ,CAAC"}
1
+ {"version":3,"file":"compose.js","names":["compose"],"sources":["../../src/react/compose.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { ComponentType } from 'react'\nimport { coreUiFeature } from '../core/core-feature'\nimport { EditorCommandPalette } from './EditorCommandPalette'\nimport { aiFeature } from '../features/ai/react'\nimport { authorsFeature } from '../features/authors/react'\nimport { blockUiFeature } from '../features/block-ui/react'\nimport { collabFeature } from '../features/collab/react'\nimport { commentsFeature } from '../features/comments/react'\nimport { compositeFeature } from '../features/composites/react'\nimport { mediaFeature } from '../features/media/react'\nimport { themesFeature } from '../features/themes/react'\nimport { transferFeature } from '../features/transfer/react'\nimport { transcriptFeature } from '../features/transcript/react'\nimport { bulletsTransform, compositeAiFeature, summaryTransform } from '../integrations/composite-ai'\nimport { transcriptAiFeature } from '../integrations/transcript-ai'\nimport { type ComposedEditor, type ComposeOptions, composeEditor as compose, type UiBindings } from '../presets/compose'\n\ntype ReactComponent = ComponentType<{ editor: Editor }>\n\n/** The React bindings of every UI-bound catalog feature. */\nconst REACT_BINDINGS = {\n 'core-ui': () => coreUiFeature<ReactComponent>({ palette: EditorCommandPalette }),\n 'media': mediaFeature,\n 'transcript': transcriptFeature,\n 'composites': compositeFeature,\n 'authors': authorsFeature,\n 'comments': commentsFeature,\n 'ai': aiFeature,\n 'composite-ai': compositeAiFeature,\n 'transcript-ai': transcriptAiFeature,\n 'collab': collabFeature,\n 'block-ui': blockUiFeature,\n 'themes': themesFeature,\n 'transfer': transferFeature,\n 'transfer-ai': () => [summaryTransform, bulletsTransform],\n} satisfies UiBindings<ReactComponent>\n\n/**\n * `composeEditor` of `presets/compose` over the React bindings: the editor of the three dimensions (`content`,\n * `interaction`, `ai`), pruned by the capabilities of `options.backend`.\n */\nexport const composeEditor = (options: ComposeOptions<ReactComponent>): ComposedEditor<ReactComponent> => compose(REACT_BINDINGS, options)\n\n/**\n * The advanced editor on the React surface: `{ content: 'rich', interaction: 'full', ai: true }`, every dimension option\n * overridable (`compositeKinds`, `backend`, `add`, `remove`, ...), so the schema is the one of `advancedSchemaExtensions()`.\n */\nexport const advancedEditor = (options: Partial<ComposeOptions<ReactComponent>> = {}): ComposedEditor<ReactComponent> => composeEditor({ content: 'rich', interaction: 'full', ai: true, ...options })\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,iBAAiB;CACrB,iBAAiB,cAA8B,EAAE,SAAS,qBAAqB,CAAC;CAChF,SAAS;CACT,cAAc;CACd,cAAc;CACd,WAAW;CACX,YAAY;CACZ,MAAM;CACN,gBAAgB;CAChB,iBAAiB;CACjB,UAAU;CACV,YAAY;CACZ,UAAU;CACV,YAAY;CACZ,qBAAqB,CAAC,kBAAkB,gBAAgB;AAC1D;;;;;AAMA,MAAa,iBAAiB,YAA4EA,gBAAQ,gBAAgB,OAAO;;;;;AAMzI,MAAa,kBAAkB,UAAmD,CAAC,MAAsC,cAAc;CAAE,SAAS;CAAQ,aAAa;CAAQ,IAAI;CAAM,GAAG;AAAQ,CAAC"}
@@ -1,6 +1,9 @@
1
1
  import { EditorBackend } from "../core/backend.js";
2
+ import { BlockUiOptions } from "../core/extension-points.js";
3
+ import { CoreContentOptions } from "../core/schema/index.js";
2
4
  import { EditorSurfaceHandle, EditorSurfaceProps, ReactFeature } from "./EditorSurface.js";
3
5
  import { CompositeKind } from "../features/composites/kinds.js";
6
+ import { Basket } from "../features/composites/baskets.js";
4
7
  import { ComposedEditor, ContentLevel, FeatureId, InteractionLevel } from "../presets/compose.js";
5
8
  import { Editor } from "@tiptap/core";
6
9
  import { ComponentType } from "react";
@@ -34,6 +37,12 @@ export interface ComposedEditorProps extends Omit<EditorSurfaceProps, 'preset'>
34
37
  backend?: EditorBackend;
35
38
  /** Composite kinds („Bausteine“) when `content` includes composites (default: `defaultCompositeKinds(t)`). */
36
39
  compositeKinds?: readonly CompositeKind[];
40
+ /** Colour baskets a Baustein can sit in when `content` includes composites (default: `defaultBaskets(t)`). */
41
+ compositeBaskets?: readonly Basket[];
42
+ /** Which surfaces render block actions: the hover bar (default), the selected-block toolbar, `'both'` or `'none'` (`BlockUiOptions`). */
43
+ blockUi?: BlockUiOptions;
44
+ /** Optional structures the content base drops from the schema: headings or heading levels, lists, blockquote, code block, rule (`CoreContentOptions`). */
45
+ coreContent?: CoreContentOptions;
37
46
  }
38
47
  //#endregion
39
48
  //# sourceMappingURL=composed-surface.d.ts.map
@@ -26,7 +26,7 @@ const ReadySurface = forwardRef(function ReadySurface({ preset, ...surface }, re
26
26
  * then nothing renders. Passing the host's own `collaboration` session keeps `collab` and `comments` whatever the
27
27
  * backend offers.
28
28
  */
29
- const ComposedSurface = forwardRef(function ComposedSurface({ content, interaction, ai, add, remove, backend, compositeKinds, ...surface }, ref) {
29
+ const ComposedSurface = forwardRef(function ComposedSurface({ content, interaction, ai, add, remove, backend, compositeKinds, compositeBaskets, blockUi, coreContent, ...surface }, ref) {
30
30
  const app = useEditorContext();
31
31
  const [source] = useState(() => backend ?? app.backend);
32
32
  const [compose] = useState(() => {
@@ -36,6 +36,9 @@ const ComposedSurface = forwardRef(function ComposedSurface({ content, interacti
36
36
  ai,
37
37
  collaboration: surface.collaboration !== void 0,
38
38
  ...compositeKinds && { compositeKinds },
39
+ ...compositeBaskets && { compositeBaskets },
40
+ ...blockUi && { blockUi },
41
+ ...coreContent && { coreContent },
39
42
  ...add && { add },
40
43
  ...remove && { remove }
41
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"composed-surface.js","names":[],"sources":["../../src/react/composed-surface.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type ComponentType, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorBackend } from '../core/backend'\nimport type { CompositeKind } from '../features/composites/kinds'\nimport type { ComposedEditor, ContentLevel, FeatureId, InteractionLevel } from '../presets/compose'\nimport { composeEditor } from './compose'\nimport { useEditorContext } from './context'\nimport { EditorSurface, type EditorSurfaceHandle, type EditorSurfaceProps, type ReactFeature } from './EditorSurface'\n\n/** The preset a composed editor mounts: the three dimensions' features, with `omitted` listing what the backend or `remove` dropped. */\nexport type ReactComposedPreset = ComposedEditor<ComponentType<{ editor: Editor }>>\n\n/** The handle of a composed editor: the surface's (`editor`, `openSettings`) plus the preset it composed. */\nexport interface ComposedEditorHandle extends EditorSurfaceHandle {\n readonly preset: ReactComposedPreset\n}\n\n/**\n * The surface's own props (collaboration session or standalone `value`/`onChange`, metadata, abilities, extensions,\n * block actions, commands, `documentId`, `editable`, `settingsUi`, the open state of the sidebar and comments margin,\n * `onReady` and the other callbacks) except the preset, plus the composition. Every composition prop is read once at\n * mount; hosts remount (`key`) to change one.\n */\nexport interface ComposedEditorProps extends Omit<EditorSurfaceProps, 'preset'> {\n /** Document model: a level or the content feature ids to install. */\n content?: ContentLevel | readonly FeatureId[]\n /** Editing UX: a level or the interaction feature ids; `[]` = no interaction (a viewer). */\n interaction?: InteractionLevel | readonly FeatureId[]\n /** Installs the AI feature and the integrations whose content is present. */\n ai?: boolean\n /** Host features, appended after the catalog. */\n add?: readonly ReactFeature[]\n /** Features (catalog or host) left out, with the features that require them. */\n remove?: readonly FeatureId[]\n /** The backend the composition is pruned by (default: the editor context's backend). */\n backend?: EditorBackend\n /** Composite kinds („Bausteine“) when `content` includes composites (default: `defaultCompositeKinds(t)`). */\n compositeKinds?: readonly CompositeKind[]\n}\n\n/** `EditorSurface` over an already composed preset; hands out the preset through the ref. */\nconst ReadySurface = forwardRef<ComposedEditorHandle, Omit<EditorSurfaceProps, 'preset'> & { preset: ReactComposedPreset }>(function ReadySurface({ preset, ...surface }, ref) {\n const surfaceRef = useRef<EditorSurfaceHandle>(null)\n useImperativeHandle(ref, () => ({\n get editor() { return surfaceRef.current?.editor ?? null },\n openSettings: tab => surfaceRef.current?.openSettings(tab),\n preset,\n }), [preset])\n return <EditorSurface ref={surfaceRef} preset={preset} {...surface} />\n})\n\n/**\n * `EditorSurface` over the preset `composeEditor` builds from the composition props, at mount. A backend that probes\n * its capabilities (`backend.ready`) is awaited first, so the composition is pruned by what it really offers; until\n * then nothing renders. Passing the host's own `collaboration` session keeps `collab` and `comments` whatever the\n * backend offers.\n */\nexport const ComposedSurface = forwardRef<ComposedEditorHandle, ComposedEditorProps & Required<Pick<ComposedEditorProps, 'content' | 'interaction' | 'ai'>>>(function ComposedSurface(\n { content, interaction, ai, add, remove, backend, compositeKinds, ...surface },\n ref,\n) {\n const app = useEditorContext()\n // Read once at mount, like every composition prop, although the preset may only be built after `backend.ready`.\n const [source] = useState(() => backend ?? app.backend)\n const [compose] = useState(() => {\n const options = {\n content,\n interaction,\n ai,\n collaboration: surface.collaboration !== undefined,\n ...(compositeKinds && { compositeKinds }),\n ...(add && { add }),\n ...(remove && { remove }),\n }\n return () => composeEditor({ ...options, backend: source })\n })\n const [preset, setPreset] = useState<ReactComposedPreset | undefined>(() => source.ready ? undefined : compose())\n useEffect(() => {\n if (preset || !source.ready) return\n let live = true\n void source.ready.then(() => { if (live) setPreset(compose()) })\n return () => { live = false }\n }, [preset, source, compose])\n return preset ? <ReadySurface ref={ref} preset={preset} {...surface} /> : null\n})\n"],"mappings":";;;;;;;AAyCA,MAAM,eAAe,WAAuG,SAAS,aAAa,EAAE,QAAQ,GAAG,WAAW,KAAK;CAC7K,MAAM,aAAa,OAA4B,IAAI;CACnD,oBAAoB,YAAY;EAC9B,IAAI,SAAS;GAAE,OAAO,WAAW,SAAS,UAAU;EAAK;EACzD,eAAc,QAAO,WAAW,SAAS,aAAa,GAAG;EACzD;CACF,IAAI,CAAC,MAAM,CAAC;CACZ,OAAO,oBAAC,eAAD;EAAe,KAAK;EAAoB;EAAQ,GAAI;CAAU,CAAA;AACvE,CAAC;;;;;;;AAQD,MAAa,kBAAkB,WAA8H,SAAS,gBACpK,EAAE,SAAS,aAAa,IAAI,KAAK,QAAQ,SAAS,gBAAgB,GAAG,WACrE,KACA;CACA,MAAM,MAAM,iBAAiB;CAE7B,MAAM,CAAC,UAAU,eAAe,WAAW,IAAI,OAAO;CACtD,MAAM,CAAC,WAAW,eAAe;EAC/B,MAAM,UAAU;GACd;GACA;GACA;GACA,eAAe,QAAQ,kBAAkB,KAAA;GACzC,GAAI,kBAAkB,EAAE,eAAe;GACvC,GAAI,OAAO,EAAE,IAAI;GACjB,GAAI,UAAU,EAAE,OAAO;EACzB;EACA,aAAa,cAAc;GAAE,GAAG;GAAS,SAAS;EAAO,CAAC;CAC5D,CAAC;CACD,MAAM,CAAC,QAAQ,aAAa,eAAgD,OAAO,QAAQ,KAAA,IAAY,QAAQ,CAAC;CAChH,gBAAgB;EACd,IAAI,UAAU,CAAC,OAAO,OAAO;EAC7B,IAAI,OAAO;EACX,OAAY,MAAM,WAAW;GAAE,IAAI,MAAM,UAAU,QAAQ,CAAC;EAAE,CAAC;EAC/D,aAAa;GAAE,OAAO;EAAM;CAC9B,GAAG;EAAC;EAAQ;EAAQ;CAAO,CAAC;CAC5B,OAAO,SAAS,oBAAC,cAAD;EAAmB;EAAa;EAAQ,GAAI;CAAU,CAAA,IAAI;AAC5E,CAAC"}
1
+ {"version":3,"file":"composed-surface.js","names":[],"sources":["../../src/react/composed-surface.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type ComponentType, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorBackend } from '../core/backend'\nimport type { BlockUiOptions } from '../core/extension-points'\nimport type { Basket } from '../features/composites/baskets'\nimport type { CompositeKind } from '../features/composites/kinds'\nimport type { CoreContentOptions } from '../core/schema'\nimport type { ComposedEditor, ContentLevel, FeatureId, InteractionLevel } from '../presets/compose'\nimport { composeEditor } from './compose'\nimport { useEditorContext } from './context'\nimport { EditorSurface, type EditorSurfaceHandle, type EditorSurfaceProps, type ReactFeature } from './EditorSurface'\n\n/** The preset a composed editor mounts: the three dimensions' features, with `omitted` listing what the backend or `remove` dropped. */\nexport type ReactComposedPreset = ComposedEditor<ComponentType<{ editor: Editor }>>\n\n/** The handle of a composed editor: the surface's (`editor`, `openSettings`) plus the preset it composed. */\nexport interface ComposedEditorHandle extends EditorSurfaceHandle {\n readonly preset: ReactComposedPreset\n}\n\n/**\n * The surface's own props (collaboration session or standalone `value`/`onChange`, metadata, abilities, extensions,\n * block actions, commands, `documentId`, `editable`, `settingsUi`, the open state of the sidebar and comments margin,\n * `onReady` and the other callbacks) except the preset, plus the composition. Every composition prop is read once at\n * mount; hosts remount (`key`) to change one.\n */\nexport interface ComposedEditorProps extends Omit<EditorSurfaceProps, 'preset'> {\n /** Document model: a level or the content feature ids to install. */\n content?: ContentLevel | readonly FeatureId[]\n /** Editing UX: a level or the interaction feature ids; `[]` = no interaction (a viewer). */\n interaction?: InteractionLevel | readonly FeatureId[]\n /** Installs the AI feature and the integrations whose content is present. */\n ai?: boolean\n /** Host features, appended after the catalog. */\n add?: readonly ReactFeature[]\n /** Features (catalog or host) left out, with the features that require them. */\n remove?: readonly FeatureId[]\n /** The backend the composition is pruned by (default: the editor context's backend). */\n backend?: EditorBackend\n /** Composite kinds („Bausteine“) when `content` includes composites (default: `defaultCompositeKinds(t)`). */\n compositeKinds?: readonly CompositeKind[]\n /** Colour baskets a Baustein can sit in when `content` includes composites (default: `defaultBaskets(t)`). */\n compositeBaskets?: readonly Basket[]\n /** Which surfaces render block actions: the hover bar (default), the selected-block toolbar, `'both'` or `'none'` (`BlockUiOptions`). */\n blockUi?: BlockUiOptions\n /** Optional structures the content base drops from the schema: headings or heading levels, lists, blockquote, code block, rule (`CoreContentOptions`). */\n coreContent?: CoreContentOptions\n}\n\n/** `EditorSurface` over an already composed preset; hands out the preset through the ref. */\nconst ReadySurface = forwardRef<ComposedEditorHandle, Omit<EditorSurfaceProps, 'preset'> & { preset: ReactComposedPreset }>(function ReadySurface({ preset, ...surface }, ref) {\n const surfaceRef = useRef<EditorSurfaceHandle>(null)\n useImperativeHandle(ref, () => ({\n get editor() { return surfaceRef.current?.editor ?? null },\n openSettings: tab => surfaceRef.current?.openSettings(tab),\n preset,\n }), [preset])\n return <EditorSurface ref={surfaceRef} preset={preset} {...surface} />\n})\n\n/**\n * `EditorSurface` over the preset `composeEditor` builds from the composition props, at mount. A backend that probes\n * its capabilities (`backend.ready`) is awaited first, so the composition is pruned by what it really offers; until\n * then nothing renders. Passing the host's own `collaboration` session keeps `collab` and `comments` whatever the\n * backend offers.\n */\nexport const ComposedSurface = forwardRef<ComposedEditorHandle, ComposedEditorProps & Required<Pick<ComposedEditorProps, 'content' | 'interaction' | 'ai'>>>(function ComposedSurface(\n { content, interaction, ai, add, remove, backend, compositeKinds, compositeBaskets, blockUi, coreContent, ...surface },\n ref,\n) {\n const app = useEditorContext()\n // Read once at mount, like every composition prop, although the preset may only be built after `backend.ready`.\n const [source] = useState(() => backend ?? app.backend)\n const [compose] = useState(() => {\n const options = {\n content,\n interaction,\n ai,\n collaboration: surface.collaboration !== undefined,\n ...(compositeKinds && { compositeKinds }),\n ...(compositeBaskets && { compositeBaskets }),\n ...(blockUi && { blockUi }),\n ...(coreContent && { coreContent }),\n ...(add && { add }),\n ...(remove && { remove }),\n }\n return () => composeEditor({ ...options, backend: source })\n })\n const [preset, setPreset] = useState<ReactComposedPreset | undefined>(() => source.ready ? undefined : compose())\n useEffect(() => {\n if (preset || !source.ready) return\n let live = true\n void source.ready.then(() => { if (live) setPreset(compose()) })\n return () => { live = false }\n }, [preset, source, compose])\n return preset ? <ReadySurface ref={ref} preset={preset} {...surface} /> : null\n})\n"],"mappings":";;;;;;;AAkDA,MAAM,eAAe,WAAuG,SAAS,aAAa,EAAE,QAAQ,GAAG,WAAW,KAAK;CAC7K,MAAM,aAAa,OAA4B,IAAI;CACnD,oBAAoB,YAAY;EAC9B,IAAI,SAAS;GAAE,OAAO,WAAW,SAAS,UAAU;EAAK;EACzD,eAAc,QAAO,WAAW,SAAS,aAAa,GAAG;EACzD;CACF,IAAI,CAAC,MAAM,CAAC;CACZ,OAAO,oBAAC,eAAD;EAAe,KAAK;EAAoB;EAAQ,GAAI;CAAU,CAAA;AACvE,CAAC;;;;;;;AAQD,MAAa,kBAAkB,WAA8H,SAAS,gBACpK,EAAE,SAAS,aAAa,IAAI,KAAK,QAAQ,SAAS,gBAAgB,kBAAkB,SAAS,aAAa,GAAG,WAC7G,KACA;CACA,MAAM,MAAM,iBAAiB;CAE7B,MAAM,CAAC,UAAU,eAAe,WAAW,IAAI,OAAO;CACtD,MAAM,CAAC,WAAW,eAAe;EAC/B,MAAM,UAAU;GACd;GACA;GACA;GACA,eAAe,QAAQ,kBAAkB,KAAA;GACzC,GAAI,kBAAkB,EAAE,eAAe;GACvC,GAAI,oBAAoB,EAAE,iBAAiB;GAC3C,GAAI,WAAW,EAAE,QAAQ;GACzB,GAAI,eAAe,EAAE,YAAY;GACjC,GAAI,OAAO,EAAE,IAAI;GACjB,GAAI,UAAU,EAAE,OAAO;EACzB;EACA,aAAa,cAAc;GAAE,GAAG;GAAS,SAAS;EAAO,CAAC;CAC5D,CAAC;CACD,MAAM,CAAC,QAAQ,aAAa,eAAgD,OAAO,QAAQ,KAAA,IAAY,QAAQ,CAAC;CAChH,gBAAgB;EACd,IAAI,UAAU,CAAC,OAAO,OAAO;EAC7B,IAAI,OAAO;EACX,OAAY,MAAM,WAAW;GAAE,IAAI,MAAM,UAAU,QAAQ,CAAC;EAAE,CAAC;EAC/D,aAAa;GAAE,OAAO;EAAM;CAC9B,GAAG;EAAC;EAAQ;EAAQ;CAAO,CAAC;CAC5B,OAAO,SAAS,oBAAC,cAAD;EAAmB;EAAa;EAAQ,GAAI;CAAU,CAAA,IAAI;AAC5E,CAAC"}
@@ -2,14 +2,11 @@ import { ModelSlot } from "../settings/values.js";
2
2
  import "../settings/index.js";
3
3
  import { PresetConfig, TranscriptionConfig } from "../config/index.js";
4
4
  import { ModelProfile, ModelRef } from "../core/backend-types.js";
5
+ import { ChatMessage } from "../features/ai/protocol/generate.js";
5
6
  import "../features/ai/protocol/index.js";
6
7
  import { HttpError } from "./errors.js";
7
8
  import { LanguageModel, ModelMessage, ToolChoice, ToolSet } from "ai";
8
9
  //#region src/server/ai.d.ts
9
- export interface ChatMessage {
10
- role: 'system' | 'user' | 'assistant';
11
- content: string;
12
- }
13
10
  /** A streamed text delta, or a finished tool call (`invalid`: unparsable input or unknown tool). */
14
11
  export type ChatTurnPart = {
15
12
  type: 'text';
@@ -86,5 +83,5 @@ export declare function streamChatTurn(opts: {
86
83
  toolChoice?: ToolChoice<ToolSet>;
87
84
  }): AsyncGenerator<ChatTurnPart>;
88
85
  //#endregion
89
- export type { PresetConfig, TranscriptionConfig };
86
+ export type { ChatMessage, PresetConfig, TranscriptionConfig };
90
87
  //# sourceMappingURL=ai.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","names":[],"sources":["../../src/server/ai.ts"],"sourcesContent":["import { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport { APICallError, type LanguageModel, type ModelMessage, streamText, type ToolChoice, type ToolSet } from 'ai'\nimport type { ModelProfile, ModelRef } from '../features/ai/protocol'\nimport type { ModelSlot } from '../settings'\nimport type { PresetConfig, TranscriptionConfig } from '../config'\nimport { HttpError } from './errors'\n\nexport interface ChatMessage { role: 'system' | 'user' | 'assistant', content: string }\n\n/** A streamed text delta, or a finished tool call (`invalid`: unparsable input or unknown tool). */\nexport type ChatTurnPart =\n | { type: 'text', text: string }\n | { type: 'tool-call', toolCallId: string, toolName: string, input: unknown, invalid: boolean }\n\nexport type { PresetConfig, TranscriptionConfig }\n\n/** Everything the AI endpoints need, passed explicitly: no runtime config, no globals. */\nexport interface AiModelsConfig {\n /** Server-defined model presets, keyed by preset id. */\n presets: Record<string, PresetConfig>\n /** Shared key of presets without their own. */\n apiKey?: string\n /** Default preset per model slot, answered by `GET /api/ai/models`. */\n slots?: Record<ModelSlot, string>\n /** Speech-to-text for audio/video composites; absent = the transcription endpoint answers 501. */\n transcription?: TranscriptionConfig\n /**\n * The host's own language model for `ref` (`ModelProvider.languageModel`), consulted before the presets for every\n * model reference; `user` is what `authenticate` returned. `null` or nothing falls through to the presets.\n */\n languageModel?: (ref: ModelRef, user: object | null) => Promise<LanguageModel | null | undefined>\n /** The models listed for `user` instead of the presets (`ModelProvider.listModels`); `null` or nothing lists the presets. */\n listModels?: (user: object | null) => Promise<ModelProfile[] | null | undefined>\n /** Appended to the error of a preset without key, e.g. the environment variable names that set it. */\n missingKeyHint?: (presetId: string) => string\n}\n\nexport interface AiModels {\n /**\n * AI SDK language model for `ref` (an OpenAI-compatible chat completions endpoint), for direct `ai` calls. The\n * host's `languageModel(ref, user)` is asked first (`user`: what `authenticate` returned); a model it returns is\n * used as is, `null` (or no such hook) falls through to the configured presets. A preset uses its own key or the\n * shared `apiKey`; a user ref brings its own. Throws `HttpError` 400 for an unknown preset and 500 for a preset\n * without key, so routes can call it before streaming starts.\n */\n resolveModel(ref: ModelRef, user: object | null): Promise<LanguageModel>\n /**\n * The configured presets as the client may see them: without their keys. `user` is what the host's `authenticate`\n * returned; the `listModels` hook supplies the list when it returns one (replacing the presets).\n */\n listModels(user: object | null): Promise<ModelProfile[]>\n readonly slots: Record<ModelSlot, string> | undefined\n readonly transcription: TranscriptionConfig | undefined\n streamChat: typeof streamChat\n streamChatTurn: typeof streamChatTurn\n}\n\n/** The AI model utilities over an explicit configuration. */\nexport function createAiModels(config: AiModelsConfig): AiModels {\n return {\n async resolveModel(ref, user) {\n const hosted = await config.languageModel?.(ref, user)\n if (hosted) return hosted\n if (!('preset' in ref)) return createOpenAICompatible({ name: 'tiptap-editor', baseURL: ref.endpoint, apiKey: ref.apiKey }).chatModel(ref.model)\n const preset = config.presets[ref.preset]\n if (!preset) throw new HttpError(400, 'Unbekanntes Modell-Preset')\n const apiKey = preset.apiKey || config.apiKey\n if (!apiKey) throw new HttpError(500, `API-Key für Preset „${ref.preset}“ fehlt${config.missingKeyHint?.(ref.preset) ?? ''}`)\n return createOpenAICompatible({ name: 'tiptap-editor', baseURL: preset.endpoint, apiKey }).chatModel(preset.model)\n },\n async listModels(user) {\n const listed = await config.listModels?.(user)\n if (listed) return listed\n return Object.entries(config.presets).map(([id, { label, endpoint, model }]) => ({ id, label, endpoint, model, source: 'preset' }))\n },\n slots: config.slots,\n transcription: config.transcription,\n streamChat,\n streamChatTurn,\n }\n}\n\nexport function upstreamError(error: unknown) {\n let detail: string\n if (APICallError.isInstance(error)) detail = [error.statusCode, error.responseBody || error.message].filter(Boolean).join(' ')\n else if (error instanceof Error) detail = error.message\n else detail = (error as { message?: string } | null)?.message ?? JSON.stringify(error)\n return new HttpError(502, `KI-Endpunkt: ${detail}`.slice(0, 500))\n}\n\n/**\n * Streams the text deltas of a chat completion (AI SDK `streamText`) on the resolved language model `model` (see\n * `resolveModel`). System messages may appear in `messages`; a trailing assistant message is a prefill the model\n * continues. Upstream failures throw a 502 `HttpError`; aborting `signal` cancels the upstream request and makes the\n * generator throw.\n */\nexport async function* streamChat(opts: { model: LanguageModel, messages: ChatMessage[], signal: AbortSignal }): AsyncGenerator<string> {\n for await (const part of streamChatTurn(opts)) {\n if (part.type === 'text') yield part.text\n }\n}\n\n/**\n * Like `streamChat`, with `tools` the model may call (tools without `execute`: the turn ends with the calls, which are\n * yielded for the caller to handle) and structured `messages` (tool calls and results in the history).\n */\nexport async function* streamChatTurn(opts: {\n model: LanguageModel\n messages: ModelMessage[]\n signal: AbortSignal\n tools?: ToolSet\n toolChoice?: ToolChoice<ToolSet>\n}): AsyncGenerator<ChatTurnPart> {\n const { model } = opts\n // Also cancels the upstream request when the consumer stops iterating early (break / return).\n const upstream = new AbortController()\n const onAbort = () => upstream.abort(opts.signal.reason)\n if (opts.signal.aborted) onAbort()\n else opts.signal.addEventListener('abort', onAbort, { once: true })\n try {\n const result = streamText({\n model,\n messages: opts.messages,\n tools: opts.tools,\n toolChoice: opts.toolChoice,\n allowSystemInMessages: true,\n abortSignal: upstream.signal,\n maxRetries: 0,\n // Errors surface as `error` parts below and are rethrown; the default handler would only log them.\n onError: () => {},\n })\n for await (const part of result.stream) {\n if (part.type === 'text-delta') {\n if (part.text) yield { type: 'text', text: part.text }\n }\n else if (part.type === 'tool-call') {\n yield { type: 'tool-call', toolCallId: part.toolCallId, toolName: part.toolName, input: part.input, invalid: part.invalid === true }\n }\n else if (part.type === 'error') {\n if (opts.signal.aborted) break\n throw upstreamError(part.error)\n }\n else if (part.type === 'abort') {\n break\n }\n }\n opts.signal.throwIfAborted()\n }\n finally {\n opts.signal.removeEventListener('abort', onAbort)\n upstream.abort()\n }\n}\n"],"mappings":";;;;;AA0DA,SAAgB,eAAe,QAAkC;CAC/D,OAAO;EACL,MAAM,aAAa,KAAK,MAAM;GAC5B,MAAM,SAAS,MAAM,OAAO,gBAAgB,KAAK,IAAI;GACrD,IAAI,QAAQ,OAAO;GACnB,IAAI,EAAE,YAAY,MAAM,OAAO,uBAAuB;IAAE,MAAM;IAAiB,SAAS,IAAI;IAAU,QAAQ,IAAI;GAAO,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK;GAC/I,MAAM,SAAS,OAAO,QAAQ,IAAI;GAClC,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,2BAA2B;GACjE,MAAM,SAAS,OAAO,UAAU,OAAO;GACvC,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,uBAAuB,IAAI,OAAO,SAAS,OAAO,iBAAiB,IAAI,MAAM,KAAK,IAAI;GAC5H,OAAO,uBAAuB;IAAE,MAAM;IAAiB,SAAS,OAAO;IAAU;GAAO,CAAC,CAAC,CAAC,UAAU,OAAO,KAAK;EACnH;EACA,MAAM,WAAW,MAAM;GACrB,MAAM,SAAS,MAAM,OAAO,aAAa,IAAI;GAC7C,IAAI,QAAQ,OAAO;GACnB,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,UAAU,cAAc;IAAE;IAAI;IAAO;IAAU;IAAO,QAAQ;GAAS,EAAE;EACpI;EACA,OAAO,OAAO;EACd,eAAe,OAAO;EACtB;EACA;CACF;AACF;AAEA,SAAgB,cAAc,OAAgB;CAC5C,IAAI;CACJ,IAAI,aAAa,WAAW,KAAK,GAAG,SAAS,CAAC,MAAM,YAAY,MAAM,gBAAgB,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;MACxH,IAAI,iBAAiB,OAAO,SAAS,MAAM;MAC3C,SAAU,OAAuC,WAAW,KAAK,UAAU,KAAK;CACrF,OAAO,IAAI,UAAU,KAAK,gBAAgB,SAAS,MAAM,GAAG,GAAG,CAAC;AAClE;;;;;;;AAQA,gBAAuB,WAAW,MAAsG;CACtI,WAAW,MAAM,QAAQ,eAAe,IAAI,GAC1C,IAAI,KAAK,SAAS,QAAQ,MAAM,KAAK;AAEzC;;;;;AAMA,gBAAuB,eAAe,MAML;CAC/B,MAAM,EAAE,UAAU;CAElB,MAAM,WAAW,IAAI,gBAAgB;CACrC,MAAM,gBAAgB,SAAS,MAAM,KAAK,OAAO,MAAM;CACvD,IAAI,KAAK,OAAO,SAAS,QAAQ;MAC5B,KAAK,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAClE,IAAI;EACF,MAAM,SAAS,WAAW;GACxB;GACA,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,YAAY,KAAK;GACjB,uBAAuB;GACvB,aAAa,SAAS;GACtB,YAAY;GAEZ,eAAe,CAAC;EAClB,CAAC;EACD,WAAW,MAAM,QAAQ,OAAO,QAC9B,IAAI,KAAK,SAAS,cACZ;OAAA,KAAK,MAAM,MAAM;IAAE,MAAM;IAAQ,MAAM,KAAK;GAAK;EAAA,OAElD,IAAI,KAAK,SAAS,aACrB,MAAM;GAAE,MAAM;GAAa,YAAY,KAAK;GAAY,UAAU,KAAK;GAAU,OAAO,KAAK;GAAO,SAAS,KAAK,YAAY;EAAK;OAEhI,IAAI,KAAK,SAAS,SAAS;GAC9B,IAAI,KAAK,OAAO,SAAS;GACzB,MAAM,cAAc,KAAK,KAAK;EAChC,OACK,IAAI,KAAK,SAAS,SACrB;EAGJ,KAAK,OAAO,eAAe;CAC7B,UACQ;EACN,KAAK,OAAO,oBAAoB,SAAS,OAAO;EAChD,SAAS,MAAM;CACjB;AACF"}
1
+ {"version":3,"file":"ai.js","names":[],"sources":["../../src/server/ai.ts"],"sourcesContent":["import { createOpenAICompatible } from '@ai-sdk/openai-compatible'\nimport { APICallError, type LanguageModel, type ModelMessage, streamText, type ToolChoice, type ToolSet } from 'ai'\nimport type { ChatMessage, ModelProfile, ModelRef } from '../features/ai/protocol'\nimport type { ModelSlot } from '../settings'\nimport type { PresetConfig, TranscriptionConfig } from '../config'\nimport { HttpError } from './errors'\n\nexport type { ChatMessage }\n\n/** A streamed text delta, or a finished tool call (`invalid`: unparsable input or unknown tool). */\nexport type ChatTurnPart =\n | { type: 'text', text: string }\n | { type: 'tool-call', toolCallId: string, toolName: string, input: unknown, invalid: boolean }\n\nexport type { PresetConfig, TranscriptionConfig }\n\n/** Everything the AI endpoints need, passed explicitly: no runtime config, no globals. */\nexport interface AiModelsConfig {\n /** Server-defined model presets, keyed by preset id. */\n presets: Record<string, PresetConfig>\n /** Shared key of presets without their own. */\n apiKey?: string\n /** Default preset per model slot, answered by `GET /api/ai/models`. */\n slots?: Record<ModelSlot, string>\n /** Speech-to-text for audio/video composites; absent = the transcription endpoint answers 501. */\n transcription?: TranscriptionConfig\n /**\n * The host's own language model for `ref` (`ModelProvider.languageModel`), consulted before the presets for every\n * model reference; `user` is what `authenticate` returned. `null` or nothing falls through to the presets.\n */\n languageModel?: (ref: ModelRef, user: object | null) => Promise<LanguageModel | null | undefined>\n /** The models listed for `user` instead of the presets (`ModelProvider.listModels`); `null` or nothing lists the presets. */\n listModels?: (user: object | null) => Promise<ModelProfile[] | null | undefined>\n /** Appended to the error of a preset without key, e.g. the environment variable names that set it. */\n missingKeyHint?: (presetId: string) => string\n}\n\nexport interface AiModels {\n /**\n * AI SDK language model for `ref` (an OpenAI-compatible chat completions endpoint), for direct `ai` calls. The\n * host's `languageModel(ref, user)` is asked first (`user`: what `authenticate` returned); a model it returns is\n * used as is, `null` (or no such hook) falls through to the configured presets. A preset uses its own key or the\n * shared `apiKey`; a user ref brings its own. Throws `HttpError` 400 for an unknown preset and 500 for a preset\n * without key, so routes can call it before streaming starts.\n */\n resolveModel(ref: ModelRef, user: object | null): Promise<LanguageModel>\n /**\n * The configured presets as the client may see them: without their keys. `user` is what the host's `authenticate`\n * returned; the `listModels` hook supplies the list when it returns one (replacing the presets).\n */\n listModels(user: object | null): Promise<ModelProfile[]>\n readonly slots: Record<ModelSlot, string> | undefined\n readonly transcription: TranscriptionConfig | undefined\n streamChat: typeof streamChat\n streamChatTurn: typeof streamChatTurn\n}\n\n/** The AI model utilities over an explicit configuration. */\nexport function createAiModels(config: AiModelsConfig): AiModels {\n return {\n async resolveModel(ref, user) {\n const hosted = await config.languageModel?.(ref, user)\n if (hosted) return hosted\n if (!('preset' in ref)) return createOpenAICompatible({ name: 'tiptap-editor', baseURL: ref.endpoint, apiKey: ref.apiKey }).chatModel(ref.model)\n const preset = config.presets[ref.preset]\n if (!preset) throw new HttpError(400, 'Unbekanntes Modell-Preset')\n const apiKey = preset.apiKey || config.apiKey\n if (!apiKey) throw new HttpError(500, `API-Key für Preset „${ref.preset}“ fehlt${config.missingKeyHint?.(ref.preset) ?? ''}`)\n return createOpenAICompatible({ name: 'tiptap-editor', baseURL: preset.endpoint, apiKey }).chatModel(preset.model)\n },\n async listModels(user) {\n const listed = await config.listModels?.(user)\n if (listed) return listed\n return Object.entries(config.presets).map(([id, { label, endpoint, model }]) => ({ id, label, endpoint, model, source: 'preset' }))\n },\n slots: config.slots,\n transcription: config.transcription,\n streamChat,\n streamChatTurn,\n }\n}\n\nexport function upstreamError(error: unknown) {\n let detail: string\n if (APICallError.isInstance(error)) detail = [error.statusCode, error.responseBody || error.message].filter(Boolean).join(' ')\n else if (error instanceof Error) detail = error.message\n else detail = (error as { message?: string } | null)?.message ?? JSON.stringify(error)\n return new HttpError(502, `KI-Endpunkt: ${detail}`.slice(0, 500))\n}\n\n/**\n * Streams the text deltas of a chat completion (AI SDK `streamText`) on the resolved language model `model` (see\n * `resolveModel`). System messages may appear in `messages`; a trailing assistant message is a prefill the model\n * continues. Upstream failures throw a 502 `HttpError`; aborting `signal` cancels the upstream request and makes the\n * generator throw.\n */\nexport async function* streamChat(opts: { model: LanguageModel, messages: ChatMessage[], signal: AbortSignal }): AsyncGenerator<string> {\n for await (const part of streamChatTurn(opts)) {\n if (part.type === 'text') yield part.text\n }\n}\n\n/**\n * Like `streamChat`, with `tools` the model may call (tools without `execute`: the turn ends with the calls, which are\n * yielded for the caller to handle) and structured `messages` (tool calls and results in the history).\n */\nexport async function* streamChatTurn(opts: {\n model: LanguageModel\n messages: ModelMessage[]\n signal: AbortSignal\n tools?: ToolSet\n toolChoice?: ToolChoice<ToolSet>\n}): AsyncGenerator<ChatTurnPart> {\n const { model } = opts\n // Also cancels the upstream request when the consumer stops iterating early (break / return).\n const upstream = new AbortController()\n const onAbort = () => upstream.abort(opts.signal.reason)\n if (opts.signal.aborted) onAbort()\n else opts.signal.addEventListener('abort', onAbort, { once: true })\n try {\n const result = streamText({\n model,\n messages: opts.messages,\n tools: opts.tools,\n toolChoice: opts.toolChoice,\n allowSystemInMessages: true,\n abortSignal: upstream.signal,\n maxRetries: 0,\n // Errors surface as `error` parts below and are rethrown; the default handler would only log them.\n onError: () => {},\n })\n for await (const part of result.stream) {\n if (part.type === 'text-delta') {\n if (part.text) yield { type: 'text', text: part.text }\n }\n else if (part.type === 'tool-call') {\n yield { type: 'tool-call', toolCallId: part.toolCallId, toolName: part.toolName, input: part.input, invalid: part.invalid === true }\n }\n else if (part.type === 'error') {\n if (opts.signal.aborted) break\n throw upstreamError(part.error)\n }\n else if (part.type === 'abort') {\n break\n }\n }\n opts.signal.throwIfAborted()\n }\n finally {\n opts.signal.removeEventListener('abort', onAbort)\n upstream.abort()\n }\n}\n"],"mappings":";;;;;AA0DA,SAAgB,eAAe,QAAkC;CAC/D,OAAO;EACL,MAAM,aAAa,KAAK,MAAM;GAC5B,MAAM,SAAS,MAAM,OAAO,gBAAgB,KAAK,IAAI;GACrD,IAAI,QAAQ,OAAO;GACnB,IAAI,EAAE,YAAY,MAAM,OAAO,uBAAuB;IAAE,MAAM;IAAiB,SAAS,IAAI;IAAU,QAAQ,IAAI;GAAO,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK;GAC/I,MAAM,SAAS,OAAO,QAAQ,IAAI;GAClC,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,2BAA2B;GACjE,MAAM,SAAS,OAAO,UAAU,OAAO;GACvC,IAAI,CAAC,QAAQ,MAAM,IAAI,UAAU,KAAK,uBAAuB,IAAI,OAAO,SAAS,OAAO,iBAAiB,IAAI,MAAM,KAAK,IAAI;GAC5H,OAAO,uBAAuB;IAAE,MAAM;IAAiB,SAAS,OAAO;IAAU;GAAO,CAAC,CAAC,CAAC,UAAU,OAAO,KAAK;EACnH;EACA,MAAM,WAAW,MAAM;GACrB,MAAM,SAAS,MAAM,OAAO,aAAa,IAAI;GAC7C,IAAI,QAAQ,OAAO;GACnB,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,UAAU,cAAc;IAAE;IAAI;IAAO;IAAU;IAAO,QAAQ;GAAS,EAAE;EACpI;EACA,OAAO,OAAO;EACd,eAAe,OAAO;EACtB;EACA;CACF;AACF;AAEA,SAAgB,cAAc,OAAgB;CAC5C,IAAI;CACJ,IAAI,aAAa,WAAW,KAAK,GAAG,SAAS,CAAC,MAAM,YAAY,MAAM,gBAAgB,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;MACxH,IAAI,iBAAiB,OAAO,SAAS,MAAM;MAC3C,SAAU,OAAuC,WAAW,KAAK,UAAU,KAAK;CACrF,OAAO,IAAI,UAAU,KAAK,gBAAgB,SAAS,MAAM,GAAG,GAAG,CAAC;AAClE;;;;;;;AAQA,gBAAuB,WAAW,MAAsG;CACtI,WAAW,MAAM,QAAQ,eAAe,IAAI,GAC1C,IAAI,KAAK,SAAS,QAAQ,MAAM,KAAK;AAEzC;;;;;AAMA,gBAAuB,eAAe,MAML;CAC/B,MAAM,EAAE,UAAU;CAElB,MAAM,WAAW,IAAI,gBAAgB;CACrC,MAAM,gBAAgB,SAAS,MAAM,KAAK,OAAO,MAAM;CACvD,IAAI,KAAK,OAAO,SAAS,QAAQ;MAC5B,KAAK,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAClE,IAAI;EACF,MAAM,SAAS,WAAW;GACxB;GACA,UAAU,KAAK;GACf,OAAO,KAAK;GACZ,YAAY,KAAK;GACjB,uBAAuB;GACvB,aAAa,SAAS;GACtB,YAAY;GAEZ,eAAe,CAAC;EAClB,CAAC;EACD,WAAW,MAAM,QAAQ,OAAO,QAC9B,IAAI,KAAK,SAAS,cACZ;OAAA,KAAK,MAAM,MAAM;IAAE,MAAM;IAAQ,MAAM,KAAK;GAAK;EAAA,OAElD,IAAI,KAAK,SAAS,aACrB,MAAM;GAAE,MAAM;GAAa,YAAY,KAAK;GAAY,UAAU,KAAK;GAAU,OAAO,KAAK;GAAO,SAAS,KAAK,YAAY;EAAK;OAEhI,IAAI,KAAK,SAAS,SAAS;GAC9B,IAAI,KAAK,OAAO,SAAS;GACzB,MAAM,cAAc,KAAK,KAAK;EAChC,OACK,IAAI,KAAK,SAAS,SACrB;EAGJ,KAAK,OAAO,eAAe;CAC7B,UACQ;EACN,KAAK,OAAO,oBAAoB,SAAS,OAAO;EAChD,SAAS,MAAM;CACjB;AACF"}
@@ -1,3 +1,6 @@
1
+ import { generateBodySchema } from "../../features/ai/protocol/generate.js";
2
+ import { completeBodySchema } from "../../features/ai/protocol/complete.js";
3
+ import "../../features/ai/protocol/index.js";
1
4
  import { AiHandlerContext, HandlerRequest, HandlerResponse } from "./context.js";
2
5
  import { z } from "zod";
3
6
  //#region src/server/handlers/ai.d.ts
@@ -8,9 +11,9 @@ export declare const chatBodySchema: z.ZodObject<{
8
11
  feedback: z.ZodOptional<z.ZodArray<z.ZodObject<{
9
12
  op: z.ZodString;
10
13
  type: z.ZodEnum<{
11
- add: "add";
12
- delete: "delete";
13
14
  update: "update";
15
+ delete: "delete";
16
+ add: "add";
14
17
  convert: "convert";
15
18
  }>;
16
19
  target: z.ZodNullable<z.ZodString>;
@@ -107,83 +110,8 @@ export declare const chatBodySchema: z.ZodObject<{
107
110
  * before `done`), `done`, `error`.
108
111
  */
109
112
  export declare function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
110
- /** Body schema of `completePost`. */
111
- export declare const completeBodySchema: z.ZodObject<{
112
- before: z.ZodString;
113
- model: z.ZodUnion<readonly [z.ZodObject<{
114
- preset: z.ZodString;
115
- }, z.core.$strip>, z.ZodObject<{
116
- endpoint: z.ZodString;
117
- model: z.ZodString;
118
- apiKey: z.ZodString;
119
- }, z.core.$strip>]>;
120
- }, z.core.$strip>;
121
113
  /** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */
122
114
  export declare function completePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
123
- /** Body schema of `generatePost`. */
124
- export declare const generateBodySchema: z.ZodObject<{
125
- task: z.ZodEnum<{
126
- custom: "custom";
127
- generate: "generate";
128
- continue: "continue";
129
- reformulate: "reformulate";
130
- "transcript-summary": "transcript-summary";
131
- "transcript-minutes": "transcript-minutes";
132
- "transcript-action-items": "transcript-action-items";
133
- fix: "fix";
134
- shorter: "shorter";
135
- formal: "formal";
136
- casual: "casual";
137
- translate: "translate";
138
- summarize: "summarize";
139
- "fill-block": "fill-block";
140
- }>;
141
- prompt: z.ZodDefault<z.ZodString>;
142
- selection: z.ZodDefault<z.ZodString>;
143
- context: z.ZodDefault<z.ZodString>;
144
- language: z.ZodOptional<z.ZodEnum<{
145
- Deutsch: "Deutsch";
146
- Englisch: "Englisch";
147
- Französisch: "Französisch";
148
- Spanisch: "Spanisch";
149
- Italienisch: "Italienisch";
150
- }>>;
151
- blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
152
- id: z.ZodString;
153
- label: z.ZodString;
154
- description: z.ZodString;
155
- syntax: z.ZodString;
156
- example: z.ZodString;
157
- }, z.core.$strip>>>;
158
- block: z.ZodOptional<z.ZodObject<{
159
- kind: z.ZodObject<{
160
- id: z.ZodString;
161
- label: z.ZodString;
162
- purpose: z.ZodOptional<z.ZodString>;
163
- }, z.core.$strip>;
164
- path: z.ZodArray<z.ZodString>;
165
- title: z.ZodString;
166
- description: z.ZodString;
167
- fields: z.ZodArray<z.ZodObject<{
168
- label: z.ZodString;
169
- value: z.ZodString;
170
- }, z.core.$strip>>;
171
- instructions: z.ZodString;
172
- skeleton: z.ZodString;
173
- body: z.ZodString;
174
- data: z.ZodArray<z.ZodObject<{
175
- label: z.ZodString;
176
- text: z.ZodString;
177
- }, z.core.$strip>>;
178
- }, z.core.$strip>>;
179
- model: z.ZodUnion<readonly [z.ZodObject<{
180
- preset: z.ZodString;
181
- }, z.core.$strip>, z.ZodObject<{
182
- endpoint: z.ZodString;
183
- model: z.ZodString;
184
- apiKey: z.ZodString;
185
- }, z.core.$strip>]>;
186
- }, z.core.$strip>;
187
115
  /** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */
188
116
  export declare function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
189
117
  /**
@@ -197,8 +125,8 @@ export declare const extractBodySchema: z.ZodObject<{
197
125
  documentId: z.ZodString;
198
126
  name: z.ZodOptional<z.ZodString>;
199
127
  method: z.ZodEnum<{
200
- transcription: "transcription";
201
128
  description: "description";
129
+ transcription: "transcription";
202
130
  bibliography: "bibliography";
203
131
  auto: "auto";
204
132
  "pdf-text": "pdf-text";
@@ -244,4 +172,5 @@ export declare const transcriptCleanupBodySchema: z.ZodObject<{
244
172
  */
245
173
  export declare function transcriptCleanupPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
246
174
  //#endregion
175
+ export { completeBodySchema, generateBodySchema };
247
176
  //# sourceMappingURL=ai.d.ts.map
@@ -1,20 +1,21 @@
1
- import { AI_LANGUAGES, STRUCTURE_MODES } from "../../settings/values.js";
2
- import { HttpError } from "../errors.js";
3
- import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
4
- import { authenticated, enforceRateLimit, eventStream, json, port, readBody } from "./context.js";
1
+ import { modelRefSchema } from "../../features/ai/protocol/models.js";
2
+ import { completeBodySchema, runCompletion } from "../../features/ai/protocol/complete.js";
3
+ import { STRUCTURE_MODES } from "../../settings/values.js";
4
+ import { AI_TASKS } from "../../features/ai/protocol/tasks.js";
5
+ import { AI_ID_PATTERN, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
6
+ import { generateBodySchema, generateProblem, runGenerate } from "../../features/ai/protocol/generate.js";
5
7
  import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
6
8
  import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
7
9
  import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
8
- import { AI_COMPLETE_LIMITS, cleanCompletion, completeMessages, completionReady } from "../../features/ai/protocol/complete.js";
9
- import { modelRefSchema } from "../../features/ai/protocol/models.js";
10
- import { AI_LIMITS, AI_TASKS, AI_TASK_IDS } from "../../features/ai/protocol/tasks.js";
11
- import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
12
- import { TRANSCRIPT_CLEANUP_LIMITS, parseTranscriptCleanup, transcriptCleanupChunks, transcriptCleanupMessages, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
10
+ import { TranscriptCleanupUnreadableError, runTranscriptCleanup, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
11
+ import { HttpError } from "../errors.js";
12
+ import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
13
+ import { authenticated, enforceRateLimit, eventStream, json, port, readBody } from "./context.js";
13
14
  import { DOI_PATTERN } from "../crossref.js";
14
15
  import { MAX_TRANSCRIBE_BYTES, RasterError, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, ocrPages, pdfPagesText, transcribe } from "../extract.js";
15
16
  import { openPdf } from "../pdf.js";
16
- import { createHash, randomUUID } from "node:crypto";
17
17
  import { z } from "zod";
18
+ import { createHash, randomUUID } from "node:crypto";
18
19
  import { jsonSchema, tool } from "ai";
19
20
  //#region src/server/handlers/ai.ts
20
21
  const blockId = z.string().min(1).max(100);
@@ -255,76 +256,34 @@ async function chatPost(ctx, request) {
255
256
  }
256
257
  });
257
258
  }
258
- /** Body schema of `completePost`. */
259
- const completeBodySchema = z.object({
260
- before: z.string().max(AI_COMPLETE_LIMITS.before),
261
- model: modelRefSchema
262
- });
263
259
  /** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */
264
260
  async function completePost(ctx, request) {
265
261
  const user = await enforceRateLimit(ctx, request);
266
262
  const { before, model } = await readBody(request, completeBodySchema);
267
263
  const languageModel = await ctx.ai.resolveModel(model, user);
268
- if (!before.trim()) return json({ text: "" });
269
264
  const abort = new AbortController();
270
265
  request.signal?.addEventListener("abort", () => abort.abort(), { once: true });
271
- let raw = "";
272
- try {
273
- for await (const text of ctx.ai.streamChat({
274
- model: languageModel,
275
- messages: completeMessages(before),
276
- signal: abort.signal
277
- })) {
278
- raw += text;
279
- if (completionReady(raw)) break;
280
- }
281
- } catch (error) {
282
- if (abort.signal.aborted) return json({ text: "" });
283
- throw error;
284
- }
285
- return json({ text: cleanCompletion(raw, before) });
266
+ return json({ text: await runCompletion(before, (messages, signal) => ctx.ai.streamChat({
267
+ model: languageModel,
268
+ messages,
269
+ signal
270
+ }), abort.signal) });
286
271
  }
287
- /** Body schema of `generatePost`. */
288
- const generateBodySchema = z.object({
289
- task: z.enum(AI_TASK_IDS),
290
- prompt: z.string().max(AI_LIMITS.prompt).default(""),
291
- selection: z.string().max(AI_LIMITS.selection).default(""),
292
- context: z.string().max(AI_LIMITS.transcript).default(""),
293
- language: z.enum(AI_LANGUAGES).optional(),
294
- blocks: z.array(aiBlockSchema).max(50).default([]),
295
- block: aiBlockInputSchema.optional(),
296
- model: modelRefSchema
297
- });
298
272
  /** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */
299
273
  async function generatePost(ctx, request) {
300
274
  const user = await enforceRateLimit(ctx, request);
301
275
  const { task: taskId, model, ...input } = await readBody(request, generateBodySchema);
302
276
  const languageModel = await ctx.ai.resolveModel(model, user);
303
277
  const task = AI_TASKS[taskId];
304
- if (task.needsSelection && !input.selection.trim()) throw new HttpError(400, "Keine Textauswahl");
305
- if (task.needsPrompt && !input.prompt.trim()) throw new HttpError(400, "Prompt fehlt");
306
- if (task.needsLanguage && !input.language) throw new HttpError(400, "Zielsprache fehlt");
307
- if (task.needsBlock && !input.block) throw new HttpError(400, "Baustein fehlt");
308
- if (task.wholeDocument && !input.context.trim()) throw new HttpError(400, "Dokument ist leer");
309
- if (!task.wholeDocument && input.context.length > AI_LIMITS.context) throw new HttpError(400, "Kontext zu lang");
278
+ const problem = generateProblem(task, input);
279
+ if (problem) throw new HttpError(400, problem);
310
280
  const users = task.needsUsers ? (await port(ctx, "directory").listUsers()).map((u) => u.username) : void 0;
311
281
  return eventStream(request, async (emit, signal) => {
312
- try {
313
- for await (const text of ctx.ai.streamChat({
314
- model: languageModel,
315
- messages: task.messages({
316
- ...input,
317
- users
318
- }),
319
- signal
320
- })) emit("delta", JSON.stringify({ text }));
321
- emit("done", "{}");
322
- } catch (error) {
323
- if (!signal.aborted) {
324
- const { statusMessage, message } = error;
325
- emit("error", JSON.stringify({ message: statusMessage ?? message ?? "Unbekannter Fehler" }));
326
- }
327
- }
282
+ for await (const { event, data } of runGenerate(task, input, users, (messages, streamSignal) => ctx.ai.streamChat({
283
+ model: languageModel,
284
+ messages,
285
+ signal: streamSignal
286
+ }), signal)) emit(event, JSON.stringify(data));
328
287
  });
329
288
  }
330
289
  /**
@@ -520,38 +479,15 @@ async function transcriptCleanupPost(ctx, request) {
520
479
  const languageModel = await ctx.ai.resolveModel(model, user);
521
480
  const abort = new AbortController();
522
481
  request.signal?.addEventListener("abort", () => abort.abort(), { once: true });
523
- const cleanChunk = async (chunk) => {
524
- let raw = "";
525
- for await (const text of ctx.ai.streamChat({
526
- model: languageModel,
527
- messages: transcriptCleanupMessages(chunk),
528
- signal: abort.signal
529
- })) raw += text;
530
- try {
531
- return parseTranscriptCleanup(raw, chunk);
532
- } catch {
533
- return null;
534
- }
535
- };
536
- const chunks = transcriptCleanupChunks(segments);
537
- const results = [];
538
- let next = 0;
539
- const worker = async () => {
540
- while (next < chunks.length) {
541
- const index = next++;
542
- results[index] = await cleanChunk(chunks[index]);
543
- }
544
- };
545
482
  try {
546
- await Promise.all(Array.from({ length: Math.min(TRANSCRIPT_CLEANUP_LIMITS.concurrency, chunks.length) }, worker));
547
- } finally {
548
- abort.abort();
483
+ return json(await runTranscriptCleanup(segments, (messages, signal) => ctx.ai.streamChat({
484
+ model: languageModel,
485
+ messages,
486
+ signal
487
+ }), abort.signal));
488
+ } catch (error) {
489
+ throw error instanceof TranscriptCleanupUnreadableError ? new HttpError(502, error.message) : error;
549
490
  }
550
- if (results.every((r) => r === null)) throw new HttpError(502, "KI-Antwort konnte nicht gelesen werden");
551
- return json({
552
- cleaned: results.flatMap((r) => r ?? []),
553
- failed: chunks.reduce((sum, chunk, i) => sum + (results[i] === null ? chunk.length : 0), 0)
554
- });
555
491
  }
556
492
  //#endregion
557
493
  export { chatBodySchema, chatPost, completeBodySchema, completePost, extractBodySchema, extractPost, generateBodySchema, generatePost, modelsGet, transcriptCleanupBodySchema, transcriptCleanupPost };