@ecsplico/editor 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -3
  3. package/dist/adapters/anchors.d.ts +80 -0
  4. package/dist/adapters/anchors.js +270 -0
  5. package/dist/adapters/anchors.js.map +1 -0
  6. package/dist/adapters/comments/client.d.ts +64 -0
  7. package/dist/adapters/comments/client.js +536 -0
  8. package/dist/adapters/comments/client.js.map +1 -0
  9. package/dist/adapters/comments/drizzle.d.ts +430 -0
  10. package/dist/adapters/comments/drizzle.js +236 -0
  11. package/dist/adapters/comments/drizzle.js.map +1 -0
  12. package/dist/adapters/comments/errors.d.ts +9 -0
  13. package/dist/adapters/comments/errors.js +19 -0
  14. package/dist/adapters/comments/errors.js.map +1 -0
  15. package/dist/adapters/comments/json.d.ts +19 -0
  16. package/dist/adapters/comments/json.js +94 -0
  17. package/dist/adapters/comments/json.js.map +1 -0
  18. package/dist/adapters/comments/record.js +78 -0
  19. package/dist/adapters/comments/record.js.map +1 -0
  20. package/dist/adapters/comments/server.d.ts +23 -0
  21. package/dist/adapters/comments/server.js +102 -0
  22. package/dist/adapters/comments/server.js.map +1 -0
  23. package/dist/adapters/comments/types.d.ts +109 -0
  24. package/dist/adapters/documents.js +108 -0
  25. package/dist/adapters/documents.js.map +1 -0
  26. package/dist/adapters/drizzle.d.ts +293 -0
  27. package/dist/adapters/drizzle.js +157 -0
  28. package/dist/adapters/drizzle.js.map +1 -0
  29. package/dist/adapters/files.js +30 -0
  30. package/dist/adapters/files.js.map +1 -0
  31. package/dist/adapters/forge.d.ts +58 -0
  32. package/dist/adapters/forge.js +91 -0
  33. package/dist/adapters/forge.js.map +1 -0
  34. package/dist/adapters/index.d.ts +13 -0
  35. package/dist/adapters/index.js +0 -0
  36. package/dist/adapters/json.d.ts +24 -0
  37. package/dist/adapters/json.js +128 -0
  38. package/dist/adapters/json.js.map +1 -0
  39. package/dist/adapters/nitro.d.ts +61 -0
  40. package/dist/adapters/nitro.js +212 -0
  41. package/dist/adapters/nitro.js.map +1 -0
  42. package/dist/adapters/routes.js +16 -0
  43. package/dist/adapters/routes.js.map +1 -0
  44. package/dist/adapters/shared.d.ts +37 -0
  45. package/dist/adapters/shared.js +32 -0
  46. package/dist/adapters/shared.js.map +1 -0
  47. package/dist/adapters/vue.d.ts +67 -0
  48. package/dist/adapters/vue.js +187 -0
  49. package/dist/adapters/vue.js.map +1 -0
  50. package/dist/code/CodeSurface.vue.d.ts +3 -3
  51. package/dist/core/doc-anchors.d.ts +5 -0
  52. package/dist/core/doc-anchors.js +4 -3
  53. package/dist/core/doc-anchors.js.map +1 -1
  54. package/dist/core/editor-ui.js +57 -1
  55. package/dist/core/editor-ui.js.map +1 -1
  56. package/dist/core/surface-session.js +139 -0
  57. package/dist/core/surface-session.js.map +1 -0
  58. package/dist/document/DocumentSurface.d.ts +1 -1
  59. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  60. package/dist/features/ai/agent-op-review.d.ts +2 -2
  61. package/dist/features/ai/agent-op-review.js.map +1 -1
  62. package/dist/features/ai/agent-ops.d.ts +1 -1
  63. package/dist/features/ai/agent-ops.js +9 -6
  64. package/dist/features/ai/agent-ops.js.map +1 -1
  65. package/dist/features/ai/ai-apply.d.ts +1 -1
  66. package/dist/features/ai/ai-review.js +10 -1
  67. package/dist/features/ai/ai-review.js.map +1 -1
  68. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  69. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  71. package/dist/features/ai/components/AiSidebar.js +2 -2
  72. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  73. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  74. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  75. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  76. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  77. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  78. package/dist/features/ai/document-chat.d.ts +3 -2
  79. package/dist/features/ai/document-chat.js +16 -19
  80. package/dist/features/ai/document-chat.js.map +1 -1
  81. package/dist/features/ai/index.d.ts +5 -3
  82. package/dist/features/ai/index.js +4 -2
  83. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  84. package/dist/features/ai/protocol/agent-ops.js +65 -39
  85. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  86. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  87. package/dist/features/ai/protocol/block-lines.js +177 -0
  88. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  89. package/dist/features/ai/protocol/chat.js +10 -10
  90. package/dist/features/ai/protocol/chat.js.map +1 -1
  91. package/dist/features/ai/protocol/index.d.ts +3 -2
  92. package/dist/features/ai/protocol/index.js +3 -2
  93. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  94. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  95. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  96. package/dist/features/ai/react/AiSidebar.js +2 -2
  97. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  98. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  99. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  100. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  101. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  102. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  103. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  104. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  105. package/dist/features/ai/selection-flow.d.ts +133 -0
  106. package/dist/features/ai/selection-flow.js +283 -0
  107. package/dist/features/ai/selection-flow.js.map +1 -0
  108. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  109. package/dist/features/ai/use-document-chat.d.ts +1 -1
  110. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  111. package/dist/features/authors/feature.js +1 -1
  112. package/dist/features/authors/index.js +1 -1
  113. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  114. package/dist/features/comments/components/CommentBubble.js +1 -1
  115. package/dist/features/comments/components/CommentComposer.js +1 -1
  116. package/dist/features/comments/components/CommentThreadView.js +5 -2
  117. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  118. package/dist/features/comments/components/CommentsMargin.js +108 -230
  119. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  120. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  121. package/dist/features/comments/feature.js +1 -1
  122. package/dist/features/comments/index.d.ts +4 -2
  123. package/dist/features/comments/index.js +4 -2
  124. package/dist/features/comments/margin-controller.d.ts +44 -0
  125. package/dist/features/comments/margin-controller.js +132 -0
  126. package/dist/features/comments/margin-controller.js.map +1 -0
  127. package/dist/features/comments/margin-layout.d.ts +55 -0
  128. package/dist/features/comments/margin-layout.js +79 -0
  129. package/dist/features/comments/margin-layout.js.map +1 -0
  130. package/dist/features/comments/model.d.ts +8 -0
  131. package/dist/features/comments/model.js +60 -34
  132. package/dist/features/comments/model.js.map +1 -1
  133. package/dist/features/comments/react/CommentBubble.js +1 -1
  134. package/dist/features/comments/react/CommentComposer.js +1 -1
  135. package/dist/features/comments/react/CommentThreadView.js +2 -2
  136. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  137. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  138. package/dist/features/comments/react/CommentsMargin.js +85 -251
  139. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  140. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  141. package/dist/features/comments/react/use-comments.js +1 -1
  142. package/dist/features/comments/react/use-margin-layout.js +45 -0
  143. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  144. package/dist/features/comments/react.d.ts +1 -1
  145. package/dist/features/comments/schema.d.ts +23 -0
  146. package/dist/features/comments/schema.js.map +1 -1
  147. package/dist/features/comments/snapshot-equality.js +40 -0
  148. package/dist/features/comments/snapshot-equality.js.map +1 -0
  149. package/dist/features/comments/thread-anchor.js +33 -0
  150. package/dist/features/comments/thread-anchor.js.map +1 -0
  151. package/dist/features/comments/use-comments.d.ts +3 -1
  152. package/dist/features/comments/use-comments.js +1 -0
  153. package/dist/features/comments/use-comments.js.map +1 -1
  154. package/dist/features/comments/use-margin-layout.js +29 -0
  155. package/dist/features/comments/use-margin-layout.js.map +1 -0
  156. package/dist/features/comments/vue.d.ts +1 -1
  157. package/dist/features/composites/accepted-children.d.ts +21 -0
  158. package/dist/features/composites/accepted-children.js +126 -0
  159. package/dist/features/composites/accepted-children.js.map +1 -0
  160. package/dist/features/composites/components/CompositeDetails.js +1 -1
  161. package/dist/features/composites/components/CompositeInspector.js +38 -164
  162. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  163. package/dist/features/composites/feature.d.ts +2 -1
  164. package/dist/features/composites/feature.js +8 -2
  165. package/dist/features/composites/feature.js.map +1 -1
  166. package/dist/features/composites/index.d.ts +5 -3
  167. package/dist/features/composites/index.js +8 -6
  168. package/dist/features/composites/inspector-model.d.ts +97 -0
  169. package/dist/features/composites/inspector-model.js +255 -0
  170. package/dist/features/composites/inspector-model.js.map +1 -0
  171. package/dist/features/composites/kinds.d.ts +11 -0
  172. package/dist/features/composites/kinds.js +17 -2
  173. package/dist/features/composites/kinds.js.map +1 -1
  174. package/dist/features/composites/outline.d.ts +18 -5
  175. package/dist/features/composites/outline.js +44 -12
  176. package/dist/features/composites/outline.js.map +1 -1
  177. package/dist/features/composites/react/CompositeDetails.js +1 -1
  178. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  179. package/dist/features/composites/react/CompositeInspector.js +21 -180
  180. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  181. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  182. package/dist/features/composites/react/CompositeView.js +1 -1
  183. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  184. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  185. package/dist/features/composites/schema.d.ts +2 -2
  186. package/dist/features/composites/schema.js +2 -2
  187. package/dist/features/composites/schema.js.map +1 -1
  188. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  189. package/dist/features/composites/use-composite-inspector.js +41 -0
  190. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  191. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  192. package/dist/features/media/index.d.ts +1 -1
  193. package/dist/features/media/vue.d.ts +1 -1
  194. package/dist/features/mentions/index.d.ts +3 -3
  195. package/dist/features/mentions/index.js +3 -3
  196. package/dist/features/mentions/schema.d.ts +1 -1
  197. package/dist/features/mentions/schema.js +1 -1
  198. package/dist/features/themes/index.d.ts +1 -1
  199. package/dist/features/themes/index.js +1 -1
  200. package/dist/features/transcript/feature.js +1 -1
  201. package/dist/features/transcript/index.js +2 -2
  202. package/dist/i18n/de/ai.js +2 -0
  203. package/dist/i18n/de/ai.js.map +1 -1
  204. package/dist/i18n/de/composites.js +8 -1
  205. package/dist/i18n/de/composites.js.map +1 -1
  206. package/dist/i18n/de/tree.js +14 -1
  207. package/dist/i18n/de/tree.js.map +1 -1
  208. package/dist/i18n/de.d.ts +22 -0
  209. package/dist/i18n/en/ai.js +2 -0
  210. package/dist/i18n/en/ai.js.map +1 -1
  211. package/dist/i18n/en/composites.js +8 -1
  212. package/dist/i18n/en/composites.js.map +1 -1
  213. package/dist/i18n/en/tree.js +14 -1
  214. package/dist/i18n/en/tree.js.map +1 -1
  215. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  216. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  217. package/dist/integrations/composite-ai/index.js +2 -2
  218. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  219. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  220. package/dist/markdown/MarkdownSurface.js +2 -2
  221. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  222. package/dist/markdown/index.d.ts +2 -2
  223. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  224. package/dist/react/EditorSurface.d.ts +1 -1
  225. package/dist/react/EditorSurface.js +43 -149
  226. package/dist/react/EditorSurface.js.map +1 -1
  227. package/dist/react/EditorToolbar.js +3 -40
  228. package/dist/react/EditorToolbar.js.map +1 -1
  229. package/dist/schema/advanced.js +2 -2
  230. package/dist/server/adapter.d.ts +2 -2
  231. package/dist/server/collab.js +1 -1
  232. package/dist/server/handlers/ai.d.ts +4 -2
  233. package/dist/server/handlers/ai.js +11 -9
  234. package/dist/server/handlers/ai.js.map +1 -1
  235. package/dist/server/handlers/context.d.ts +1 -1
  236. package/dist/server/handlers/editor.d.ts +1 -1
  237. package/dist/server/handlers/editor.js +2 -2
  238. package/dist/server/host.d.ts +1 -1
  239. package/dist/server/index.d.ts +2 -2
  240. package/dist/settings/index.d.ts +1 -1
  241. package/dist/styles/tokens.css +1 -1
  242. package/dist/tree/DocumentTree.js +159 -387
  243. package/dist/tree/DocumentTree.js.map +1 -1
  244. package/dist/tree/drop-rules.js +37 -0
  245. package/dist/tree/drop-rules.js.map +1 -0
  246. package/dist/tree/react/DocumentTree.d.ts +2 -2
  247. package/dist/tree/react/DocumentTree.js +135 -397
  248. package/dist/tree/react/DocumentTree.js.map +1 -1
  249. package/dist/tree/react/use-tree-model.js +129 -0
  250. package/dist/tree/react/use-tree-model.js.map +1 -0
  251. package/dist/tree/react.js +1 -1
  252. package/dist/tree/tree-host.js +100 -0
  253. package/dist/tree/tree-host.js.map +1 -0
  254. package/dist/tree/tree-items.js +22 -43
  255. package/dist/tree/tree-items.js.map +1 -1
  256. package/dist/tree/tree-model.js +580 -0
  257. package/dist/tree/tree-model.js.map +1 -0
  258. package/dist/tree/tree-rows.js +77 -0
  259. package/dist/tree/tree-rows.js.map +1 -0
  260. package/dist/tree/use-tree-model.js +94 -0
  261. package/dist/tree/use-tree-model.js.map +1 -0
  262. package/dist/tree/vue.js +1 -1
  263. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  264. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  265. package/dist/vue/components/EditorSurface.js +27 -87
  266. package/dist/vue/components/EditorSurface.js.map +1 -1
  267. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  268. package/dist/vue/components/EditorToolbar.js +7 -44
  269. package/dist/vue/components/EditorToolbar.js.map +1 -1
  270. package/dist/vue/context.d.ts +1 -1
  271. package/dist/vue/context.js +1 -1
  272. package/dist/vue/context.js.map +1 -1
  273. package/dist/vue/use-editor-ui.d.ts +1 -1
  274. package/dist/vue/use-notifier.d.ts +1 -1
  275. package/docs/integration.md +97 -4
  276. package/docs/llms.md +9 -1
  277. package/docs/usage.md +10 -5
  278. package/package.json +67 -5
@@ -1 +1 @@
1
- {"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import { type AnyExtension, type Editor, getSchema, type JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { contributions, settingsTabsOf } from '../core/define-editor'\nimport { type EditorUi, registerEditor } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, UploadFn } from '../core/feature'\nimport { blockActionsOf, createFeatureContext } from '../core/feature-context'\nimport { createMetadataRegistry } from '../core/metadata'\nimport type { EditorMetadata, MetadataItem } from '../core/metadata-types'\nimport { presetExtension } from '../core/pipeline'\nimport { EMPTY_EDITOR_CONTENT } from '../core/schema'\nimport { SuggestionPopup } from '../core/suggestion-popup'\nimport { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/** Everything fixed at mount: preset, context, extensions, content check; callbacks are read from the surface's props. */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration } = props.current\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n if (collaboration && !preset.features.some(feature => feature.id === 'collab')) {\n throw new Error('@ecsplico/editor: `collaboration` needs a preset with the collaboration feature')\n }\n\n const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n const registry = createMetadataRegistry({ onCreate: (list, item) => props.current.onMetadataCreate?.(list, item) })\n registry.replace({ ...props.current.metadata })\n\n const ctx = createFeatureContext({\n app,\n preset,\n ui,\n metadata: registry,\n notify: reactNotifier,\n getEditor: () => parts.editor.current ?? undefined,\n ...(props.current.documentId !== undefined && { documentId: props.current.documentId }),\n ...(props.current.upload && { upload: props.current.upload }),\n ...(collaboration && { collaboration }),\n ...(props.current.abilities && { abilities: props.current.abilities }),\n ...(props.current.slashCommands && { slashCommands: props.current.slashCommands }),\n })\n\n const extensions: AnyExtension[] = [\n presetExtension(preset),\n SuggestionPopup.configure({ renderer: reactSuggestionRenderer }),\n ...contributions(preset, 'extensions').flatMap(factory => factory(ctx)),\n ...(props.current.extensions ?? []),\n ]\n const schema = getSchema(extensions)\n const overlays = preset.features.flatMap(feature => [...(feature.overlays ?? [])])\n const blockActions = blockActionsOf(preset, ctx, props.current.blockActions)\n\n /** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */\n const validated = (json: JSONContent): { json: JSONContent } | { error: Error } => {\n try {\n const migrated = preset.pipeline.migrateContent(json)\n schema.nodeFromJSON(migrated).check()\n return { json: migrated }\n }\n catch (error) {\n return { error: error instanceof Error ? error : new Error(String(error)) }\n }\n }\n\n // Standalone: the JSON last sent through `onChange`; the host handing it back is not an external change.\n let lastEmitted: JSONContent | undefined\n let content: JSONContent | undefined\n let initialError: Error | undefined\n if (!collaboration) {\n const initial = props.current.value ? validated(props.current.value) : { json: EMPTY_EDITOR_CONTENT }\n if ('error' in initial) initialError = initial.error\n content = 'json' in initial ? initial.json : EMPTY_EDITOR_CONTENT\n }\n\n return {\n preset,\n collaboration,\n ui,\n registry,\n ctx,\n extensions,\n overlays,\n blockActions,\n validated,\n content,\n initialError,\n get lastEmitted() {\n return lastEmitted\n },\n set lastEmitted(json: JSONContent | undefined) {\n lastEmitted = json\n },\n /** What the effects below already applied; they only react to a change. */\n applied: {\n value: props.current.value,\n editable: props.current.editable ?? true,\n metadata: props.current.metadata,\n },\n /** Teardowns of the `setup` contributions that are currently active. */\n teardowns: [] as (() => void)[],\n errorReported: false,\n announced: null as Editor | null,\n }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [surface] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n const { preset, collaboration } = surface\n\n const editor = useEditor({\n extensions: surface.extensions,\n ...(!collaboration && {\n content: surface.content,\n onUpdate: ({ editor: instance }: { editor: Editor }) => {\n const json = instance.getJSON()\n surface.lastEmitted = json\n latest.current.onChange?.(json)\n },\n }),\n editable,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n editorProps: { attributes: { class: 'tiptap' } },\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Run and teardown are paired, so StrictMode's mount, cleanup, mount\n // leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n for (const run of contributions(preset, 'setup')) {\n const teardown = run(surface.ctx)\n if (teardown) surface.teardowns.push(teardown)\n }\n return () => {\n for (const teardown of surface.teardowns.splice(0)) teardown()\n }\n }, [surface, preset])\n\n // The editor exists from the first render; announce it once its chrome is registered first, so a host toolbar that\n // receives it through `onReady` finds the chrome.\n useEffect(() => {\n if (surface.initialError && !surface.errorReported) {\n surface.errorReported = true\n latest.current.onContentError?.(surface.initialError)\n }\n const unregister = registerEditor(editor, { ui: surface.ui, preset, ctx: surface.ctx, blockActions: surface.blockActions })\n if (surface.announced !== editor) {\n surface.announced = editor\n latest.current.onReady?.(editor)\n }\n return unregister\n }, [editor, surface, preset])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => {\n if (value === surface.applied.value) return\n surface.applied.value = value\n if (collaboration || !value || editor.isDestroyed || value === surface.lastEmitted) return\n const checked = surface.validated(value)\n if ('error' in checked) {\n latest.current.onContentError?.(checked.error)\n return\n }\n editor.commands.setContent(checked.json, { emitUpdate: false })\n }, [value, editor, surface, collaboration])\n\n // `setEditable(…, false)` changes the flag without any event; an empty, history-less transaction lets components that\n // follow `isEditable` on `transaction` (block UI, the composite inspector) notice. It has no steps, so nothing syncs.\n useEffect(() => {\n if (editable === surface.applied.editable) return\n surface.applied.editable = editable\n if (editor.isDestroyed) return\n editor.setEditable(editable, false)\n editor.view.dispatch(editor.state.tr.setMeta('addToHistory', false))\n }, [editable, editor, surface])\n\n useEffect(() => {\n if (metadata === surface.applied.metadata) return\n surface.applied.metadata = metadata\n surface.registry.replace({ ...metadata })\n }, [metadata, surface])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={surface.ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {surface.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import type { AnyExtension, Editor, JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { settingsTabsOf } from '../core/define-editor'\nimport type { EditorUi } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, UploadFn } from '../core/feature'\nimport type { EditorMetadata, MetadataItem } from '../core/metadata-types'\nimport { createSurfaceSession } from '../core/surface-session'\nimport { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/**\n * Everything fixed at mount: the chrome handle and the framework-free session (preset, context, extensions, content\n * check, setup lifecycle); callbacks are read from the surface's latest props.\n */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration, value, editable, metadata, documentId, upload, abilities, slashCommands, extensions, blockActions } = props.current\n\n const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n const session = createSurfaceSession({\n app,\n preset,\n ui,\n notify: reactNotifier,\n suggestionRenderer: reactSuggestionRenderer,\n getEditor: () => parts.editor.current ?? undefined,\n collaboration,\n value,\n editable,\n metadata,\n documentId,\n upload,\n abilities,\n slashCommands,\n extensions,\n blockActions,\n onChange: content => props.current.onChange?.(content),\n onMetadataCreate: (list, item) => props.current.onMetadataCreate?.(list, item),\n onContentError: error => props.current.onContentError?.(error),\n onReady: editor => props.current.onReady?.(editor),\n })\n return { preset, ui, session }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [{ preset, ui, session }] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n\n const editor = useEditor({\n ...session.editorOptions,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Activation and deactivation are paired, so StrictMode's mount,\n // cleanup, mount leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n session.activate()\n return () => session.deactivate()\n }, [session])\n\n // The editor exists from the first render; its chrome is registered before the session announces it through\n // `onReady`, so a host toolbar that receives it finds the chrome.\n useEffect(() => session.register(editor), [editor, session])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => session.setValue(value), [value, editor, session])\n\n useEffect(() => session.setEditable(editable), [editable, editor, session])\n\n useEffect(() => session.setMetadata(metadata), [metadata, session])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {session.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":"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"}
@@ -1,8 +1,7 @@
1
1
  import { abilitiesAtSelection } from "../core/abilities.js";
2
- import { createMetadataRegistry } from "../core/metadata.js";
3
2
  import { mergeToolbarGroups } from "../core/toolbar.js";
4
3
  import { contributions } from "../core/define-editor.js";
5
- import { editorEntryOf, notifierOf } from "../core/editor-ui.js";
4
+ import { borrowedFeatureContext, editorEntryOf } from "../core/editor-ui.js";
6
5
  import { useEditorContext } from "./context.js";
7
6
  import { useEditorI18n } from "./use-editor-i18n.js";
8
7
  import { cn } from "./ui/utils.js";
@@ -48,48 +47,12 @@ function EditorToolbar({ editor: given, preset }) {
48
47
  const { t } = useEditorI18n();
49
48
  const [state] = useState(() => {
50
49
  const ref = { current: editor };
51
- const fallbackMetadata = createMetadataRegistry();
52
- const entry = () => editorEntryOf(ref.current);
53
- const ui = () => entry()?.ui;
54
50
  return {
55
51
  ref,
56
- ctx: {
52
+ ctx: borrowedFeatureContext({
57
53
  app,
58
- locale: app.locale,
59
- t: app.t,
60
- settings: app.settings,
61
- notify: notifierOf(() => ref.current, app),
62
- fetch: app.fetch,
63
- ai: app.ai,
64
- get metadata() {
65
- return entry()?.ctx.metadata ?? fallbackMetadata;
66
- },
67
- ui: {
68
- openSettings: (tab) => ui()?.openSettings(tab),
69
- toggleSidebar: () => ui()?.toggleSidebar(),
70
- toggleComments: () => ui()?.toggleComments(),
71
- get commentsOpen() {
72
- return ui()?.commentsOpen ?? false;
73
- }
74
- },
75
- get documentId() {
76
- return entry()?.ctx.documentId;
77
- },
78
- get upload() {
79
- return entry()?.ctx.upload;
80
- },
81
- get collaboration() {
82
- return entry()?.ctx.collaboration;
83
- },
84
- get anchorResolver() {
85
- return entry()?.ctx.anchorResolver ?? null;
86
- },
87
- get abilities() {
88
- return entry()?.ctx.abilities ?? {};
89
- },
90
- slashCommands: () => entry()?.ctx.slashCommands() ?? [],
91
54
  getEditor: () => ref.current
92
- }
55
+ })
93
56
  };
94
57
  });
95
58
  state.ref.current = editor;
@@ -1 +1 @@
1
- {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { editorEntryOf, notifierOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport { createMetadataRegistry } from '../core/metadata'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n const fallbackMetadata = createMetadataRegistry()\n const entry = () => editorEntryOf(ref.current)\n const ui = () => entry()?.ui\n const ctx: FeatureContext = {\n app,\n locale: app.locale,\n t: app.t,\n settings: app.settings,\n notify: notifierOf(() => ref.current, app),\n fetch: app.fetch,\n ai: app.ai,\n get metadata() { return entry()?.ctx.metadata ?? fallbackMetadata },\n ui: {\n openSettings: tab => ui()?.openSettings(tab),\n toggleSidebar: () => ui()?.toggleSidebar(),\n toggleComments: () => ui()?.toggleComments(),\n get commentsOpen() { return ui()?.commentsOpen ?? false },\n },\n get documentId() { return entry()?.ctx.documentId },\n get upload() { return entry()?.ctx.upload },\n get collaboration() { return entry()?.ctx.collaboration },\n get anchorResolver() { return entry()?.ctx.anchorResolver ?? null },\n get abilities() { return entry()?.ctx.abilities ?? {} },\n slashCommands: () => entry()?.ctx.slashCommands() ?? [],\n getEditor: () => ref.current,\n }\n return { ref, ctx }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { borrowedFeatureContext, editorEntryOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n return { ref, ctx: borrowedFeatureContext({ app, getEditor: () => ref.current }) }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
@@ -2,11 +2,11 @@ import { BlockId } from "../core/schema/block-ids.js";
2
2
  import { NodeFlags } from "../core/schema/node-flags.js";
3
3
  import { OpaqueFence } from "../core/schema/opaque-fence.js";
4
4
  import { StrikeX } from "../core/schema/strike.js";
5
- import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
6
5
  import { compositeSchemaExtensions } from "../features/composites/schema.js";
7
- import { mediaSchemaExtensions } from "../features/media/schema.js";
8
6
  import { authorsSchemaExtensions } from "../features/authors/schema.js";
9
7
  import { commentsSchemaExtensions } from "../features/comments/schema.js";
8
+ import { mediaSchemaExtensions } from "../features/media/schema.js";
9
+ import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
10
10
  import { tablesSchemaExtensions } from "../features/tables/schema.js";
11
11
  import { transcriptSchemaExtensions } from "../features/transcript/schema.js";
12
12
  import StarterKit from "@tiptap/starter-kit";
@@ -1,10 +1,10 @@
1
1
  import { EditorSettings } from "../settings/index.js";
2
- import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
3
- import "../features/ai/protocol/index.js";
4
2
  import { AssetRecord } from "../features/media/assets.js";
5
3
  import "../features/media/schema.js";
6
4
  import { MentionUser } from "../features/mentions/mention.js";
7
5
  import "../features/mentions/schema.js";
6
+ import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
7
+ import "../features/ai/protocol/index.js";
8
8
  import { ThemeRecord } from "../features/themes/schema.js";
9
9
  import { AnyExtension, JSONContent } from "@tiptap/core";
10
10
  import { LanguageModel } from "ai";
@@ -4,9 +4,9 @@ import { migrateLegacyContent } from "../features/composites/legacy.js";
4
4
  import { advancedSchemaExtensions } from "../schema/advanced.js";
5
5
  import { decodeCollabState, encodeCollabState, isStateStale } from "./collab-state.js";
6
6
  import { getSchema } from "@tiptap/core";
7
+ import { Hocuspocus } from "@hocuspocus/server";
7
8
  import { prosemirrorToYDoc, prosemirrorToYXmlFragment, yDocToProsemirrorJSON, yXmlFragmentToProsemirror } from "@tiptap/y-tiptap";
8
9
  import * as Y from "yjs";
9
- import { Hocuspocus } from "@hocuspocus/server";
10
10
  //#region src/server/collab.ts
11
11
  /** Y.XmlFragment the TipTap Collaboration extension binds to (its default `field`). */
12
12
  const COLLAB_FIELD = "default";
@@ -22,6 +22,7 @@ export declare const chatBodySchema: z.ZodObject<{
22
22
  pending: "pending";
23
23
  unchanged: "unchanged";
24
24
  missing: "missing";
25
+ stale: "stale";
25
26
  overlap: "overlap";
26
27
  empty: "empty";
27
28
  blocked: "blocked";
@@ -97,7 +98,8 @@ export declare const chatBodySchema: z.ZodObject<{
97
98
  }, z.core.$strip>;
98
99
  /**
99
100
  * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
100
- * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, or converting into a type not in
101
+ * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
102
+ * operation inside a block that another operation replaces or deletes, or converting into a type not in
101
103
  * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
102
104
  * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
103
105
  * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
@@ -120,8 +122,8 @@ export declare function completePost(ctx: AiHandlerContext, request: HandlerRequ
120
122
  /** Body schema of `generatePost`. */
121
123
  export declare const generateBodySchema: z.ZodObject<{
122
124
  task: z.ZodEnum<{
123
- generate: "generate";
124
125
  custom: "custom";
126
+ generate: "generate";
125
127
  continue: "continue";
126
128
  reformulate: "reformulate";
127
129
  "transcript-summary": "transcript-summary";
@@ -1,14 +1,15 @@
1
1
  import { AI_LANGUAGES, STRUCTURE_MODES } from "../../settings/values.js";
2
- import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps, documentBlockIds } from "../../features/ai/protocol/agent-ops.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, readBody } from "./context.js";
5
+ import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
6
+ import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
3
7
  import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
4
8
  import { AI_COMPLETE_LIMITS, cleanCompletion, completeMessages, completionReady } from "../../features/ai/protocol/complete.js";
5
9
  import { modelRefSchema } from "../../features/ai/protocol/models.js";
6
10
  import { AI_LIMITS, AI_TASKS, AI_TASK_IDS } from "../../features/ai/protocol/tasks.js";
7
11
  import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
8
- import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
9
12
  import { TRANSCRIPT_CLEANUP_LIMITS, parseTranscriptCleanup, transcriptCleanupChunks, transcriptCleanupMessages, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
10
- import { HttpError } from "../errors.js";
11
- import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
12
13
  import { z } from "zod";
13
14
  import { jsonSchema, tool } from "ai";
14
15
  //#region src/server/handlers/ai.ts
@@ -117,7 +118,7 @@ const agentTools = { [AGENT_TOOL]: tool({
117
118
  },
118
119
  afterId: {
119
120
  type: ["string", "null"],
120
- description: "add: ID des Blocks, nach dem eingefügt wird; null = Dokumentanfang."
121
+ description: "add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."
121
122
  },
122
123
  markdown: {
123
124
  type: "string",
@@ -136,7 +137,8 @@ const agentTools = { [AGENT_TOOL]: tool({
136
137
  }) };
137
138
  /**
138
139
  * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
139
- * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, or converting into a type not in
140
+ * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
141
+ * operation inside a block that another operation replaces or deletes, or converting into a type not in
140
142
  * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
141
143
  * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
142
144
  * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
@@ -147,11 +149,11 @@ async function chatPost(ctx, request) {
147
149
  const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema);
148
150
  const languageModel = await ctx.ai.resolveModel(model, user);
149
151
  if (!messages.at(-1).content.trim()) throw new HttpError(400, "Nachricht fehlt");
150
- const known = documentBlockIds(document);
151
- const allowed = scope ? new Set(scope.filter((id) => known.has(id))) : null;
152
+ const lines = readBlockLines(document);
153
+ const allowed = scope ? new Set(scope.filter((id) => lines.ids.has(id))) : null;
152
154
  if (allowed && !allowed.size) throw new HttpError(400, "Die Auswahl enthält keine Blöcke des Dokuments");
153
155
  const context = {
154
- known: (id) => known.has(id),
156
+ lines,
155
157
  allowed,
156
158
  targets: new Set(targets)
157
159
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","names":[],"sources":["../../../src/server/handlers/ai.ts"],"sourcesContent":["import { jsonSchema, type ModelMessage, tool } from 'ai'\nimport { z } from 'zod'\nimport {\n AGENT_LIMITS,\n AGENT_OP_OUTCOMES,\n AGENT_OP_TYPES,\n AGENT_TOOL,\n AI_COMPLETE_LIMITS,\n AI_ID_PATTERN,\n AI_LIMITS,\n AI_TASK_IDS,\n AI_TASKS,\n type AgentOp,\n type AgentOpProblem,\n agentOpTarget,\n agentRetryMessage,\n agentUnreadableProblem,\n aiBlockInputSchema,\n aiBlockSchema,\n checkAgentOps,\n CHAT_LIMITS,\n cleanCompletion,\n completeMessages,\n completionReady,\n documentBlockIds,\n documentChatMessages,\n type ModelAgentOp,\n modelRefSchema,\n} from '../../features/ai/protocol'\nimport type { CompositeMetaField } from '../../features/composites/schema'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME } from '../../features/media/schema'\nimport {\n parseTranscriptCleanup,\n TRANSCRIPT_CLEANUP_LIMITS,\n type TranscriptCleanupResponse,\n type TranscriptCleanupResult,\n type TranscriptCleanupSegment,\n transcriptCleanupChunks,\n transcriptCleanupMessages,\n transcriptCleanupRequestSchema,\n} from '../../integrations/transcript-ai/protocol'\nimport { AI_LANGUAGES, STRUCTURE_MODES } from '../../settings'\nimport { HttpError } from '../errors'\nimport { type AiHandlerContext, authenticated, enforceRateLimit, eventStream, type HandlerRequest, type HandlerResponse, json, readBody } from './context'\n\nconst blockId = z.string().min(1).max(100)\nconst opId = z.string().regex(/^op\\d+$/)\nconst markdown = z.string().min(1).max(AGENT_LIMITS.markdown)\n/** A `convert` target: a `CONVERT_TARGET_IDS` entry, `composite:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */\nconst convertTarget = z.string().min(1).max(100)\n\nconst opSchema = z.discriminatedUnion('type', [\n z.object({ op: opId, type: z.literal('update'), id: blockId, markdown }),\n z.object({ op: opId, type: z.literal('delete'), id: blockId }),\n z.object({ op: opId, type: z.literal('add'), afterId: blockId.nullable(), markdown }),\n z.object({ op: opId, type: z.literal('convert'), id: blockId, to: convertTarget }),\n])\n\nconst label = z.string().min(1).max(100)\nconst kindSchema = z.object({\n // Same characters as the `::: kind` fence opener accepts.\n id: z.string().regex(AI_ID_PATTERN).max(50),\n label,\n purpose: z.string().min(1).max(500).optional(),\n fields: z.array(z.object({\n key: z.string().min(1).max(50),\n label,\n type: z.enum(['text', 'textarea', 'date', 'number', 'select'] as const satisfies readonly CompositeMetaField['type'][]),\n options: z.array(z.object({ value: z.string().min(1).max(100), label })).max(50).optional(),\n })).max(20),\n})\n\nconst messageSchema = z.discriminatedUnion('role', [\n z.object({\n role: z.literal('user'),\n content: z.string().min(1).max(CHAT_LIMITS.message),\n feedback: z.array(z.object({ op: opId, type: z.enum(AGENT_OP_TYPES), target: blockId.nullable(), outcome: z.enum(AGENT_OP_OUTCOMES) })).max(500).optional(),\n }),\n z.object({\n role: z.literal('assistant'),\n content: z.string().max(CHAT_LIMITS.message),\n ops: z.array(opSchema).max(AGENT_LIMITS.ops).optional(),\n dropped: z.number().int().min(0).max(1000).optional(),\n }).refine(m => m.content || m.ops?.length || m.dropped, 'Leere Antwort im Verlauf'),\n])\n\nexport const chatBodySchema = z.object({\n messages: z.array(messageSchema).min(1).max(CHAT_LIMITS.messages).refine(messages => messages.at(-1)!.role === 'user', 'Die letzte Nachricht muss von der Nutzerin bzw. dem Nutzer sein'),\n document: z.string().max(CHAT_LIMITS.document),\n /** Block IDs edits are limited to (the selected blocks); null or missing: the whole document. */\n scope: z.array(blockId).min(1).max(AGENT_LIMITS.scope).nullish(),\n /** Number of the first new operation (`op<n>`), so IDs stay unique across the conversation. */\n nextOp: z.number().int().min(1).max(1_000_000).default(1),\n model: modelRefSchema,\n /** The editor's composite kinds (`compositeKindsOf`), for the block model in the system prompt. */\n kinds: z.array(kindSchema).max(50),\n /** The editor's host blocks (`aiContext.blocks`), for the block model in the system prompt. */\n blocks: z.array(aiBlockSchema).max(50).default([]),\n /** Block types `convert` may turn a block into. */\n targets: z.array(convertTarget).max(100),\n /** The user's preferred structure of new content (settings `blocks.structure`). */\n structure: z.enum(STRUCTURE_MODES),\n})\n\n/** `edit_document`: deliberately loose, so malformed calls reach `checkAgentOps` and get a precise retry message. */\nconst agentTools = {\n [AGENT_TOOL]: tool({\n description: 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.',\n inputSchema: jsonSchema<{ ops: unknown[] }>({\n type: 'object',\n properties: {\n ops: {\n type: 'array',\n description: 'Die Operationen in Dokumentreihenfolge.',\n items: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...AGENT_OP_TYPES], description: 'update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.' },\n id: { type: 'string', description: 'update/delete/convert: ID des Blocks aus dem Dokument.' },\n afterId: { type: ['string', 'null'], description: 'add: ID des Blocks, nach dem eingefügt wird; null = Dokumentanfang.' },\n markdown: { type: 'string', description: 'update/add: der neue Inhalt als Markdown, ohne Block-ID.' },\n to: { type: 'string', description: 'convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task).' },\n },\n required: ['type'],\n },\n },\n },\n required: ['ops'],\n }),\n }),\n}\n\n/**\n * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the\n * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, or converting into a type not in\n * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a\n * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like\n * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,\n * before `done`), `done`, `error`.\n */\nexport async function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!messages.at(-1)!.content.trim()) throw new HttpError(400, 'Nachricht fehlt')\n\n const known = documentBlockIds(document)\n const allowed = scope ? new Set(scope.filter(id => known.has(id))) : null\n if (allowed && !allowed.size) throw new HttpError(400, 'Die Auswahl enthält keine Blöcke des Dokuments')\n const context = { known: (id: string) => known.has(id), allowed, targets: new Set(targets) }\n\n return eventStream(request, async (emit, signal) => {\n try {\n let conversation: ModelMessage[] = documentChatMessages(messages, document, allowed && [...allowed], { kinds, blocks, targets, structure })\n let answered = false\n let result: { valid: ModelAgentOp[], problems: AgentOpProblem[] } | null = null\n // Problems of attempts that were retried: an operation the corrected list leaves out counts as dropped too.\n const retried: AgentOpProblem[] = []\n let retries = 0\n for (let attempt = 0; attempt <= AGENT_LIMITS.retries; attempt++) {\n const calls: { toolCallId: string, input: unknown, invalid: boolean }[] = []\n let text = ''\n for await (const part of ctx.ai.streamChatTurn({\n model: languageModel,\n messages: conversation,\n tools: agentTools,\n // Retries only correct the operations: no second answer text.\n toolChoice: attempt ? { type: 'tool', toolName: AGENT_TOOL } : 'auto',\n signal,\n })) {\n if (part.type === 'tool-call') {\n if (part.toolName === AGENT_TOOL) calls.push(part)\n }\n else if (!attempt) {\n text += part.text\n answered = true\n emit('delta', JSON.stringify({ text: part.text }))\n }\n }\n if (!calls.length) break\n\n const unreadable = calls.some(call => call.invalid || !Array.isArray((call.input as { ops?: unknown } | null)?.ops))\n const raw = calls.flatMap(call => (call.input as { ops?: unknown } | null)?.ops).filter(op => op !== undefined)\n const checked = checkAgentOps(raw, context)\n if (unreadable) checked.problems.unshift(agentUnreadableProblem())\n result = checked\n if (!checked.problems.length || attempt === AGENT_LIMITS.retries) break\n\n retried.push(...checked.problems)\n retries++\n const retry = agentRetryMessage(checked.problems, !!allowed)\n conversation = [\n ...conversation,\n {\n role: 'assistant',\n content: [\n ...(text ? [{ type: 'text' as const, text }] : []),\n ...calls.map(call => ({ type: 'tool-call' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, input: call.input ?? {} })),\n ],\n },\n { role: 'tool', content: calls.map(call => ({ type: 'tool-result' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, output: { type: 'text' as const, value: retry } })) },\n ]\n }\n\n if (result) {\n const ops: AgentOp[] = result.valid.map((op, i) => ({ ...op, op: `op${nextOp + i}` }))\n const targets = new Set(ops.map(op => agentOpTarget(op)))\n const dropped = [...result.problems]\n for (const problem of retried) {\n if (problem.id !== null && !targets.has(problem.id) && !dropped.some(d => d.id === problem.id && d.type === problem.type)) dropped.push(problem)\n }\n if (!answered && !ops.length) {\n emit('delta', JSON.stringify({ text: 'Ich konnte keine gültigen Änderungen vorschlagen.' }))\n }\n emit('ops', JSON.stringify({ ops, dropped, retries }))\n }\n emit('done', '{}')\n \n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n/** Body schema of `completePost`. */\nexport const completeBodySchema = z.object({ before: z.string().max(AI_COMPLETE_LIMITS.before), model: modelRefSchema })\n\n/** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */\nexport async function completePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { before, model } = await readBody(request, completeBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!before.trim()) return json({ text: '' })\n\n // Typing on the client aborts its fetch; stop the upstream request with it (breaking the loop cancels it as well).\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n let raw = ''\n try {\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: completeMessages(before), signal: abort.signal })) {\n raw += text\n if (completionReady(raw)) break\n }\n }\n catch (error) {\n if (abort.signal.aborted) return json({ text: '' })\n throw error\n }\n return json({ text: cleanCompletion(raw, before) })\n}\n\n/** Body schema of `generatePost`. */\nexport const generateBodySchema = z.object({\n task: z.enum(AI_TASK_IDS),\n prompt: z.string().max(AI_LIMITS.prompt).default(''),\n selection: z.string().max(AI_LIMITS.selection).default(''),\n // Whole-document tasks may send the longer transcript; the stricter limit for the others is checked per task below.\n context: z.string().max(AI_LIMITS.transcript).default(''),\n language: z.enum(AI_LANGUAGES).optional(),\n blocks: z.array(aiBlockSchema).max(50).default([]),\n block: aiBlockInputSchema.optional(),\n model: modelRefSchema,\n})\n\n/** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */\nexport async function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { task: taskId, model, ...input } = await readBody(request, generateBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n const task = AI_TASKS[taskId]\n if (task.needsSelection && !input.selection.trim()) throw new HttpError(400, 'Keine Textauswahl')\n if (task.needsPrompt && !input.prompt.trim()) throw new HttpError(400, 'Prompt fehlt')\n if (task.needsLanguage && !input.language) throw new HttpError(400, 'Zielsprache fehlt')\n if (task.needsBlock && !input.block) throw new HttpError(400, 'Baustein fehlt')\n if (task.wholeDocument && !input.context.trim()) throw new HttpError(400, 'Dokument ist leer')\n if (!task.wholeDocument && input.context.length > AI_LIMITS.context) throw new HttpError(400, 'Kontext zu lang')\n const users = task.needsUsers ? (await ctx.adapter.listUsers()).map(u => u.username) : undefined\n\n return eventStream(request, async (emit, signal) => {\n try {\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: task.messages({ ...input, users }), signal })) {\n emit('delta', JSON.stringify({ text }))\n }\n emit('done', '{}')\n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n/**\n * The models the client may choose from (without keys) — the adapter's `listModels` for the user, else the\n * server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an\n * authenticated user via the adapter; not rate limited, since it calls no model.\n */\nexport async function modelsGet(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n return json({ presets: await ctx.ai.listModels(user), slots: ctx.ai.slots })\n}\n\n/** Largest asset sent to the speech-to-text endpoint (the limit of OpenAI's `/audio/transcriptions`). */\nconst MAX_TRANSCRIBE_BYTES = 25 * 1024 * 1024\n\n/** Body schema of `transcribePost`. */\nexport const transcribeBodySchema = z.object({\n documentId: z.string().regex(ASSET_DOCUMENT_ID),\n name: z.string().regex(ASSET_NAME),\n})\n\n/**\n * Transcribes a stored audio/video asset through the OpenAI-compatible `<endpoint>/audio/transcriptions` configured as\n * the AI `transcription`. Plain JSON: `{ text }`. 501 while no endpoint is configured.\n */\nexport async function transcribePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await enforceRateLimit(ctx, request)\n const { documentId, name } = await readBody(request, transcribeBodySchema)\n const config = ctx.ai.transcription\n if (!config?.endpoint || !config.model) throw new HttpError(501, 'Transkription nicht konfiguriert')\n\n const asset = await ctx.adapter.loadAsset(documentId, name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n if (asset.data.byteLength > MAX_TRANSCRIBE_BYTES) {\n throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`)\n }\n\n const form = new FormData()\n form.append('file', new Blob([new Uint8Array(asset.data)], { type: asset.record.mimeType }), asset.record.filename)\n form.append('model', config.model)\n form.append('response_format', 'text')\n\n // Closing the page aborts the client's fetch; stop the upstream request with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n let response: Response\n try {\n response = await fetch(`${config.endpoint.replace(/\\/+$/, '')}/audio/transcriptions`, {\n method: 'POST',\n headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : undefined,\n body: form,\n signal: abort.signal,\n })\n }\n catch (err) {\n throw new HttpError(502, `Transkriptions-Endpunkt: ${err instanceof Error ? err.message : String(err)}`)\n }\n if (!response.ok) {\n throw new HttpError(502, `Transkriptions-Endpunkt: ${response.status} ${response.statusText}`.trim())\n }\n return json({ text: (await response.text()).trim() })\n}\n\n/** Body schema of `transcriptCleanupPost`. */\nexport const transcriptCleanupBodySchema = transcriptCleanupRequestSchema\n\n/**\n * Cleans transcript segments (filler words, punctuation, casing, fragmented sentences) without touching speakers or\n * order: the model only returns a new text per segment ID. Plain JSON: `{ cleaned, failed }`.\n */\nexport async function transcriptCleanupPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { segments, model } = await readBody(request, transcriptCleanupBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Closing the menu or the page aborts the client's fetch; stop the upstream requests with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n\n // Long transcripts are cleaned in chunks, a few model calls at a time; a chunk with an unusable answer stays as it is.\n const cleanChunk = async (chunk: TranscriptCleanupSegment[]) => {\n let raw = ''\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: transcriptCleanupMessages(chunk), signal: abort.signal })) raw += text\n try {\n return parseTranscriptCleanup(raw, chunk)\n }\n catch {\n return null\n }\n }\n const chunks = transcriptCleanupChunks(segments)\n const results: (TranscriptCleanupResult[] | null)[] = []\n let next = 0\n const worker = async () => {\n while (next < chunks.length) {\n const index = next++\n results[index] = await cleanChunk(chunks[index]!)\n }\n }\n try {\n await Promise.all(Array.from({ length: Math.min(TRANSCRIPT_CLEANUP_LIMITS.concurrency, chunks.length) }, worker))\n }\n finally {\n // An upstream error in one chunk fails the request: stop the other chunks' model calls too.\n abort.abort()\n }\n\n if (results.every(r => r === null)) throw new HttpError(502, 'KI-Antwort konnte nicht gelesen werden')\n return json({\n cleaned: results.flatMap(r => r ?? []),\n failed: chunks.reduce((sum, chunk, i) => sum + (results[i] === null ? chunk.length : 0), 0),\n } satisfies TranscriptCleanupResponse)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai.js","names":[],"sources":["../../../src/server/handlers/ai.ts"],"sourcesContent":["import { jsonSchema, type ModelMessage, tool } from 'ai'\nimport { z } from 'zod'\nimport {\n AGENT_LIMITS,\n AGENT_OP_OUTCOMES,\n AGENT_OP_TYPES,\n AGENT_TOOL,\n AI_COMPLETE_LIMITS,\n AI_ID_PATTERN,\n AI_LIMITS,\n AI_TASK_IDS,\n AI_TASKS,\n type AgentOp,\n type AgentOpProblem,\n agentOpTarget,\n agentRetryMessage,\n agentUnreadableProblem,\n aiBlockInputSchema,\n aiBlockSchema,\n checkAgentOps,\n CHAT_LIMITS,\n cleanCompletion,\n completeMessages,\n completionReady,\n documentChatMessages,\n type ModelAgentOp,\n modelRefSchema,\n readBlockLines,\n} from '../../features/ai/protocol'\nimport type { CompositeMetaField } from '../../features/composites/schema'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME } from '../../features/media/schema'\nimport {\n parseTranscriptCleanup,\n TRANSCRIPT_CLEANUP_LIMITS,\n type TranscriptCleanupResponse,\n type TranscriptCleanupResult,\n type TranscriptCleanupSegment,\n transcriptCleanupChunks,\n transcriptCleanupMessages,\n transcriptCleanupRequestSchema,\n} from '../../integrations/transcript-ai/protocol'\nimport { AI_LANGUAGES, STRUCTURE_MODES } from '../../settings'\nimport { HttpError } from '../errors'\nimport { type AiHandlerContext, authenticated, enforceRateLimit, eventStream, type HandlerRequest, type HandlerResponse, json, readBody } from './context'\n\nconst blockId = z.string().min(1).max(100)\nconst opId = z.string().regex(/^op\\d+$/)\nconst markdown = z.string().min(1).max(AGENT_LIMITS.markdown)\n/** A `convert` target: a `CONVERT_TARGET_IDS` entry, `composite:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */\nconst convertTarget = z.string().min(1).max(100)\n\nconst opSchema = z.discriminatedUnion('type', [\n z.object({ op: opId, type: z.literal('update'), id: blockId, markdown }),\n z.object({ op: opId, type: z.literal('delete'), id: blockId }),\n z.object({ op: opId, type: z.literal('add'), afterId: blockId.nullable(), markdown }),\n z.object({ op: opId, type: z.literal('convert'), id: blockId, to: convertTarget }),\n])\n\nconst label = z.string().min(1).max(100)\nconst kindSchema = z.object({\n // Same characters as the `::: kind` fence opener accepts.\n id: z.string().regex(AI_ID_PATTERN).max(50),\n label,\n purpose: z.string().min(1).max(500).optional(),\n fields: z.array(z.object({\n key: z.string().min(1).max(50),\n label,\n type: z.enum(['text', 'textarea', 'date', 'number', 'select'] as const satisfies readonly CompositeMetaField['type'][]),\n options: z.array(z.object({ value: z.string().min(1).max(100), label })).max(50).optional(),\n })).max(20),\n})\n\nconst messageSchema = z.discriminatedUnion('role', [\n z.object({\n role: z.literal('user'),\n content: z.string().min(1).max(CHAT_LIMITS.message),\n feedback: z.array(z.object({ op: opId, type: z.enum(AGENT_OP_TYPES), target: blockId.nullable(), outcome: z.enum(AGENT_OP_OUTCOMES) })).max(500).optional(),\n }),\n z.object({\n role: z.literal('assistant'),\n content: z.string().max(CHAT_LIMITS.message),\n ops: z.array(opSchema).max(AGENT_LIMITS.ops).optional(),\n dropped: z.number().int().min(0).max(1000).optional(),\n }).refine(m => m.content || m.ops?.length || m.dropped, 'Leere Antwort im Verlauf'),\n])\n\nexport const chatBodySchema = z.object({\n messages: z.array(messageSchema).min(1).max(CHAT_LIMITS.messages).refine(messages => messages.at(-1)!.role === 'user', 'Die letzte Nachricht muss von der Nutzerin bzw. dem Nutzer sein'),\n document: z.string().max(CHAT_LIMITS.document),\n /** Block IDs edits are limited to (the selected blocks); null or missing: the whole document. */\n scope: z.array(blockId).min(1).max(AGENT_LIMITS.scope).nullish(),\n /** Number of the first new operation (`op<n>`), so IDs stay unique across the conversation. */\n nextOp: z.number().int().min(1).max(1_000_000).default(1),\n model: modelRefSchema,\n /** The editor's composite kinds (`compositeKindsOf`), for the block model in the system prompt. */\n kinds: z.array(kindSchema).max(50),\n /** The editor's host blocks (`aiContext.blocks`), for the block model in the system prompt. */\n blocks: z.array(aiBlockSchema).max(50).default([]),\n /** Block types `convert` may turn a block into. */\n targets: z.array(convertTarget).max(100),\n /** The user's preferred structure of new content (settings `blocks.structure`). */\n structure: z.enum(STRUCTURE_MODES),\n})\n\n/** `edit_document`: deliberately loose, so malformed calls reach `checkAgentOps` and get a precise retry message. */\nconst agentTools = {\n [AGENT_TOOL]: tool({\n description: 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.',\n inputSchema: jsonSchema<{ ops: unknown[] }>({\n type: 'object',\n properties: {\n ops: {\n type: 'array',\n description: 'Die Operationen in Dokumentreihenfolge.',\n items: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...AGENT_OP_TYPES], description: 'update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.' },\n id: { type: 'string', description: 'update/delete/convert: ID des Blocks aus dem Dokument.' },\n afterId: { type: ['string', 'null'], description: 'add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.' },\n markdown: { type: 'string', description: 'update/add: der neue Inhalt als Markdown, ohne Block-ID.' },\n to: { type: 'string', description: 'convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task).' },\n },\n required: ['type'],\n },\n },\n },\n required: ['ops'],\n }),\n }),\n}\n\n/**\n * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the\n * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an\n * operation inside a block that another operation replaces or deletes, or converting into a type not in\n * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a\n * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like\n * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,\n * before `done`), `done`, `error`.\n */\nexport async function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!messages.at(-1)!.content.trim()) throw new HttpError(400, 'Nachricht fehlt')\n\n const lines = readBlockLines(document)\n const allowed = scope ? new Set(scope.filter(id => lines.ids.has(id))) : null\n if (allowed && !allowed.size) throw new HttpError(400, 'Die Auswahl enthält keine Blöcke des Dokuments')\n const context = { lines, allowed, targets: new Set(targets) }\n\n return eventStream(request, async (emit, signal) => {\n try {\n let conversation: ModelMessage[] = documentChatMessages(messages, document, allowed && [...allowed], { kinds, blocks, targets, structure })\n let answered = false\n let result: { valid: ModelAgentOp[], problems: AgentOpProblem[] } | null = null\n // Problems of attempts that were retried: an operation the corrected list leaves out counts as dropped too.\n const retried: AgentOpProblem[] = []\n let retries = 0\n for (let attempt = 0; attempt <= AGENT_LIMITS.retries; attempt++) {\n const calls: { toolCallId: string, input: unknown, invalid: boolean }[] = []\n let text = ''\n for await (const part of ctx.ai.streamChatTurn({\n model: languageModel,\n messages: conversation,\n tools: agentTools,\n // Retries only correct the operations: no second answer text.\n toolChoice: attempt ? { type: 'tool', toolName: AGENT_TOOL } : 'auto',\n signal,\n })) {\n if (part.type === 'tool-call') {\n if (part.toolName === AGENT_TOOL) calls.push(part)\n }\n else if (!attempt) {\n text += part.text\n answered = true\n emit('delta', JSON.stringify({ text: part.text }))\n }\n }\n if (!calls.length) break\n\n const unreadable = calls.some(call => call.invalid || !Array.isArray((call.input as { ops?: unknown } | null)?.ops))\n const raw = calls.flatMap(call => (call.input as { ops?: unknown } | null)?.ops).filter(op => op !== undefined)\n const checked = checkAgentOps(raw, context)\n if (unreadable) checked.problems.unshift(agentUnreadableProblem())\n result = checked\n if (!checked.problems.length || attempt === AGENT_LIMITS.retries) break\n\n retried.push(...checked.problems)\n retries++\n const retry = agentRetryMessage(checked.problems, !!allowed)\n conversation = [\n ...conversation,\n {\n role: 'assistant',\n content: [\n ...(text ? [{ type: 'text' as const, text }] : []),\n ...calls.map(call => ({ type: 'tool-call' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, input: call.input ?? {} })),\n ],\n },\n { role: 'tool', content: calls.map(call => ({ type: 'tool-result' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, output: { type: 'text' as const, value: retry } })) },\n ]\n }\n\n if (result) {\n const ops: AgentOp[] = result.valid.map((op, i) => ({ ...op, op: `op${nextOp + i}` }))\n const targets = new Set(ops.map(op => agentOpTarget(op)))\n const dropped = [...result.problems]\n for (const problem of retried) {\n if (problem.id !== null && !targets.has(problem.id) && !dropped.some(d => d.id === problem.id && d.type === problem.type)) dropped.push(problem)\n }\n if (!answered && !ops.length) {\n emit('delta', JSON.stringify({ text: 'Ich konnte keine gültigen Änderungen vorschlagen.' }))\n }\n emit('ops', JSON.stringify({ ops, dropped, retries }))\n }\n emit('done', '{}')\n \n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n/** Body schema of `completePost`. */\nexport const completeBodySchema = z.object({ before: z.string().max(AI_COMPLETE_LIMITS.before), model: modelRefSchema })\n\n/** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */\nexport async function completePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { before, model } = await readBody(request, completeBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!before.trim()) return json({ text: '' })\n\n // Typing on the client aborts its fetch; stop the upstream request with it (breaking the loop cancels it as well).\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n let raw = ''\n try {\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: completeMessages(before), signal: abort.signal })) {\n raw += text\n if (completionReady(raw)) break\n }\n }\n catch (error) {\n if (abort.signal.aborted) return json({ text: '' })\n throw error\n }\n return json({ text: cleanCompletion(raw, before) })\n}\n\n/** Body schema of `generatePost`. */\nexport const generateBodySchema = z.object({\n task: z.enum(AI_TASK_IDS),\n prompt: z.string().max(AI_LIMITS.prompt).default(''),\n selection: z.string().max(AI_LIMITS.selection).default(''),\n // Whole-document tasks may send the longer transcript; the stricter limit for the others is checked per task below.\n context: z.string().max(AI_LIMITS.transcript).default(''),\n language: z.enum(AI_LANGUAGES).optional(),\n blocks: z.array(aiBlockSchema).max(50).default([]),\n block: aiBlockInputSchema.optional(),\n model: modelRefSchema,\n})\n\n/** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */\nexport async function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { task: taskId, model, ...input } = await readBody(request, generateBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n const task = AI_TASKS[taskId]\n if (task.needsSelection && !input.selection.trim()) throw new HttpError(400, 'Keine Textauswahl')\n if (task.needsPrompt && !input.prompt.trim()) throw new HttpError(400, 'Prompt fehlt')\n if (task.needsLanguage && !input.language) throw new HttpError(400, 'Zielsprache fehlt')\n if (task.needsBlock && !input.block) throw new HttpError(400, 'Baustein fehlt')\n if (task.wholeDocument && !input.context.trim()) throw new HttpError(400, 'Dokument ist leer')\n if (!task.wholeDocument && input.context.length > AI_LIMITS.context) throw new HttpError(400, 'Kontext zu lang')\n const users = task.needsUsers ? (await ctx.adapter.listUsers()).map(u => u.username) : undefined\n\n return eventStream(request, async (emit, signal) => {\n try {\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: task.messages({ ...input, users }), signal })) {\n emit('delta', JSON.stringify({ text }))\n }\n emit('done', '{}')\n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n/**\n * The models the client may choose from (without keys) — the adapter's `listModels` for the user, else the\n * server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an\n * authenticated user via the adapter; not rate limited, since it calls no model.\n */\nexport async function modelsGet(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n return json({ presets: await ctx.ai.listModels(user), slots: ctx.ai.slots })\n}\n\n/** Largest asset sent to the speech-to-text endpoint (the limit of OpenAI's `/audio/transcriptions`). */\nconst MAX_TRANSCRIBE_BYTES = 25 * 1024 * 1024\n\n/** Body schema of `transcribePost`. */\nexport const transcribeBodySchema = z.object({\n documentId: z.string().regex(ASSET_DOCUMENT_ID),\n name: z.string().regex(ASSET_NAME),\n})\n\n/**\n * Transcribes a stored audio/video asset through the OpenAI-compatible `<endpoint>/audio/transcriptions` configured as\n * the AI `transcription`. Plain JSON: `{ text }`. 501 while no endpoint is configured.\n */\nexport async function transcribePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await enforceRateLimit(ctx, request)\n const { documentId, name } = await readBody(request, transcribeBodySchema)\n const config = ctx.ai.transcription\n if (!config?.endpoint || !config.model) throw new HttpError(501, 'Transkription nicht konfiguriert')\n\n const asset = await ctx.adapter.loadAsset(documentId, name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n if (asset.data.byteLength > MAX_TRANSCRIBE_BYTES) {\n throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`)\n }\n\n const form = new FormData()\n form.append('file', new Blob([new Uint8Array(asset.data)], { type: asset.record.mimeType }), asset.record.filename)\n form.append('model', config.model)\n form.append('response_format', 'text')\n\n // Closing the page aborts the client's fetch; stop the upstream request with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n let response: Response\n try {\n response = await fetch(`${config.endpoint.replace(/\\/+$/, '')}/audio/transcriptions`, {\n method: 'POST',\n headers: config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : undefined,\n body: form,\n signal: abort.signal,\n })\n }\n catch (err) {\n throw new HttpError(502, `Transkriptions-Endpunkt: ${err instanceof Error ? err.message : String(err)}`)\n }\n if (!response.ok) {\n throw new HttpError(502, `Transkriptions-Endpunkt: ${response.status} ${response.statusText}`.trim())\n }\n return json({ text: (await response.text()).trim() })\n}\n\n/** Body schema of `transcriptCleanupPost`. */\nexport const transcriptCleanupBodySchema = transcriptCleanupRequestSchema\n\n/**\n * Cleans transcript segments (filler words, punctuation, casing, fragmented sentences) without touching speakers or\n * order: the model only returns a new text per segment ID. Plain JSON: `{ cleaned, failed }`.\n */\nexport async function transcriptCleanupPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { segments, model } = await readBody(request, transcriptCleanupBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Closing the menu or the page aborts the client's fetch; stop the upstream requests with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n\n // Long transcripts are cleaned in chunks, a few model calls at a time; a chunk with an unusable answer stays as it is.\n const cleanChunk = async (chunk: TranscriptCleanupSegment[]) => {\n let raw = ''\n for await (const text of ctx.ai.streamChat({ model: languageModel, messages: transcriptCleanupMessages(chunk), signal: abort.signal })) raw += text\n try {\n return parseTranscriptCleanup(raw, chunk)\n }\n catch {\n return null\n }\n }\n const chunks = transcriptCleanupChunks(segments)\n const results: (TranscriptCleanupResult[] | null)[] = []\n let next = 0\n const worker = async () => {\n while (next < chunks.length) {\n const index = next++\n results[index] = await cleanChunk(chunks[index]!)\n }\n }\n try {\n await Promise.all(Array.from({ length: Math.min(TRANSCRIPT_CLEANUP_LIMITS.concurrency, chunks.length) }, worker))\n }\n finally {\n // An upstream error in one chunk fails the request: stop the other chunks' model calls too.\n abort.abort()\n }\n\n if (results.every(r => r === null)) throw new HttpError(502, 'KI-Antwort konnte nicht gelesen werden')\n return json({\n cleaned: results.flatMap(r => r ?? []),\n failed: chunks.reduce((sum, chunk, i) => sum + (results[i] === null ? chunk.length : 0), 0),\n } satisfies TranscriptCleanupResponse)\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { AiModels } from "../ai.js";
2
1
  import { EditorAdapter } from "../adapter.js";
2
+ import { AiModels } from "../ai.js";
3
3
  import { CollabServer } from "../collab.js";
4
4
  import { RateLimiter } from "../rate-limit.js";
5
5
  //#region src/server/handlers/context.d.ts
@@ -11,8 +11,8 @@ export declare const settingsPutBodySchema: import("zod").ZodObject<{
11
11
  }>;
12
12
  authorColors: import("zod").ZodBoolean;
13
13
  authorColorStyle: import("zod").ZodEnum<{
14
- background: "background";
15
14
  text: "text";
15
+ background: "background";
16
16
  }>;
17
17
  commentsLayout: import("zod").ZodEnum<{
18
18
  margin: "margin";
@@ -1,8 +1,8 @@
1
1
  import { THEME_ID, editorSettingsSchema, parseEditorSettings } from "../../settings/index.js";
2
- import { collectTagNames, normalizeTagName } from "../../features/mentions/tag.js";
2
+ import { HttpError } from "../errors.js";
3
3
  import { ASSET_DOCUMENT_ID, ASSET_NAME, assetUrl } from "../../features/media/assets.js";
4
4
  import { MAX_MEDIA_BYTES, MEDIA_TYPES, mediaKindOf, mediaMime } from "../../features/media/schema.js";
5
- import { HttpError } from "../errors.js";
5
+ import { collectTagNames, normalizeTagName } from "../../features/mentions/tag.js";
6
6
  import { COLLAB_FIELD } from "../collab.js";
7
7
  import { authenticated, empty, json, readBody, text } from "./context.js";
8
8
  import { MAX_THEME_BYTES, themeManifestSchema } from "../../features/themes/schema.js";
@@ -1,7 +1,7 @@
1
1
  import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
2
2
  import "../features/ai/protocol/index.js";
3
- import { AiModels, AiModelsConfig } from "./ai.js";
4
3
  import { EditorAdapter } from "./adapter.js";
4
+ import { AiModels, AiModelsConfig } from "./ai.js";
5
5
  import { CollabServer } from "./collab.js";
6
6
  import { JSONContent } from "@tiptap/core";
7
7
  import { LanguageModel } from "ai";
@@ -1,8 +1,8 @@
1
+ import { AuthenticateRequest, EditorAdapter } from "./adapter.js";
1
2
  import { HttpError } from "./errors.js";
2
3
  import { AiModels, AiModelsConfig, ChatMessage, ChatTurnPart, PresetConfig, TranscriptionConfig, createAiModels, streamChat, streamChatTurn, upstreamError } from "./ai.js";
3
- import { AuthenticateRequest, EditorAdapter } from "./adapter.js";
4
4
  import { COLLAB_FIELD, CollabConnection, CollabServer, CollabServerOptions, WebSocketLike, createCollabServer } from "./collab.js";
5
- import { RateLimiter, RateLimiterOptions, createRateLimiter, userKey } from "./rate-limit.js";
6
5
  import { decodeCollabState, encodeCollabState, isStateStale } from "./collab-state.js";
6
+ import { RateLimiter, RateLimiterOptions, createRateLimiter, userKey } from "./rate-limit.js";
7
7
  import { EditorServerConfig, closeEditorServer, collabContentChanged, collabStats, configureEditorServer, enforceAiRateLimit, forgetCollabDocument, listAiModels, liveContent, resolveModel, setEditorAdapter, useAiModels, useCollabServer, useEditorAdapter } from "./host.js";
8
8
  export { type AiModels, type AiModelsConfig, type AuthenticateRequest, COLLAB_FIELD, type ChatMessage, type ChatTurnPart, type CollabConnection, type CollabServer, type CollabServerOptions, type EditorAdapter, type EditorServerConfig, HttpError, type PresetConfig, type RateLimiter, type RateLimiterOptions, type TranscriptionConfig, type WebSocketLike, closeEditorServer, collabContentChanged, collabStats, configureEditorServer, createAiModels, createCollabServer, createRateLimiter, decodeCollabState, encodeCollabState, enforceAiRateLimit, forgetCollabDocument, isStateStale, listAiModels, liveContent, resolveModel, setEditorAdapter, streamChat, streamChatTurn, upstreamError, useAiModels, useCollabServer, useEditorAdapter, userKey };
@@ -54,8 +54,8 @@ export declare const editorSettingsSchema: z.ZodObject<{
54
54
  }>;
55
55
  authorColors: z.ZodBoolean;
56
56
  authorColorStyle: z.ZodEnum<{
57
- background: "background";
58
57
  text: "text";
58
+ background: "background";
59
59
  }>;
60
60
  commentsLayout: z.ZodEnum<{
61
61
  margin: "margin";
@@ -1,5 +1,5 @@
1
1
  /* Shared SKAILE design tokens (`--sk-*`). The editor, the annotation inbox and the agent inbox read the same variables,
2
- so a host maps its design system once (e.g. `--sk-primary: var(--ui-primary)` or `var(--p-primary-500)`). Declared
2
+ so a host maps its design system once (e.g. `--sk-primary: var(--ui-primary)`). Declared
3
3
  under `:where()` (specificity 0), so repeated declarations across packages are harmless and any host rule wins.
4
4
  Dark mode is values only: the host sets `.dark`, nothing else changes. */
5
5
  :where(:root) {