@ecsplico/editor 2.1.1 → 3.0.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.
- package/CHANGELOG.md +74 -1
- package/README.md +39 -156
- package/dist/adapters/comments/drizzle.d.ts +1 -1
- package/dist/adapters/comments/{client.js → rest-model.js} +3 -3
- package/dist/adapters/comments/rest-model.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +2 -2
- package/dist/adapters/document-state.d.ts +23 -0
- package/dist/adapters/document-state.js +66 -0
- package/dist/adapters/document-state.js.map +1 -0
- package/dist/adapters/documents.js +14 -11
- package/dist/adapters/documents.js.map +1 -1
- package/dist/adapters/drizzle.d.ts +2 -2
- package/dist/adapters/drizzle.js +97 -88
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/adapters/forge.d.ts +12 -7
- package/dist/adapters/forge.js +38 -45
- package/dist/adapters/forge.js.map +1 -1
- package/dist/adapters/http.d.ts +20 -0
- package/dist/adapters/http.js +68 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/json.d.ts +3 -3
- package/dist/adapters/json.js +58 -49
- package/dist/adapters/json.js.map +1 -1
- package/dist/adapters/memory.d.ts +21 -0
- package/dist/adapters/memory.js +53 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/nitro.d.ts +23 -20
- package/dist/adapters/nitro.js +58 -163
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/react.d.ts +55 -0
- package/dist/adapters/react.js +209 -0
- package/dist/adapters/react.js.map +1 -0
- package/dist/adapters/shared.d.ts +15 -21
- package/dist/adapters/shared.js +10 -8
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/vue.d.ts +15 -30
- package/dist/adapters/vue.js +36 -87
- package/dist/adapters/vue.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.js +95 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/{adapters/routes.js → core/api-routes.js} +5 -3
- package/dist/core/api-routes.js.map +1 -0
- package/dist/core/backend-capability.d.ts +7 -0
- package/dist/core/backend-capability.js +17 -0
- package/dist/core/backend-capability.js.map +1 -0
- package/dist/core/backend-types.d.ts +245 -0
- package/dist/core/backend-types.js +17 -0
- package/dist/core/backend-types.js.map +1 -0
- package/dist/core/backend.d.ts +108 -0
- package/dist/core/backend.js +14 -0
- package/dist/core/backend.js.map +1 -0
- package/dist/core/block-convert.d.ts +7 -5
- package/dist/core/block-convert.js +28 -5
- package/dist/core/block-convert.js.map +1 -1
- package/dist/core/command-types.d.ts +89 -0
- package/dist/core/commands.d.ts +40 -0
- package/dist/core/commands.js +151 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/context.d.ts +17 -20
- package/dist/core/context.js +24 -21
- package/dist/core/context.js.map +1 -1
- package/dist/core/core-feature.d.ts +22 -7
- package/dist/core/core-feature.js +51 -18
- package/dist/core/core-feature.js.map +1 -1
- package/dist/core/define-editor.d.ts +4 -3
- package/dist/core/define-editor.js +14 -7
- package/dist/core/define-editor.js.map +1 -1
- package/dist/core/document-markdown.js +12 -3
- package/dist/core/document-markdown.js.map +1 -1
- package/dist/core/editor-ui.d.ts +8 -1
- package/dist/core/editor-ui.js +9 -8
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/extension-points.d.ts +3 -15
- package/dist/core/feature-context.d.ts +8 -7
- package/dist/core/feature-context.js +12 -11
- package/dist/core/feature-context.js.map +1 -1
- package/dist/core/feature.d.ts +35 -19
- package/dist/core/feature.js.map +1 -1
- package/dist/core/fences.d.ts +2 -1
- package/dist/core/fences.js +3 -2
- package/dist/core/fences.js.map +1 -1
- package/dist/core/fetch-error.d.ts +1 -1
- package/dist/core/fetch-error.js +3 -1
- package/dist/core/fetch-error.js.map +1 -1
- package/dist/core/hash.d.ts +5 -0
- package/dist/core/hash.js +11 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/http-backend.d.ts +11 -0
- package/dist/core/http-backend.js +195 -0
- package/dist/core/http-backend.js.map +1 -0
- package/dist/core/index.d.ts +14 -9
- package/dist/core/index.js +12 -9
- package/dist/core/palette.d.ts +48 -0
- package/dist/core/palette.js +95 -0
- package/dist/core/palette.js.map +1 -0
- package/dist/core/schema/abilities.d.ts +2 -1
- package/dist/core/schema/abilities.js +5 -1
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/core/schema/index.d.ts +11 -5
- package/dist/core/schema/index.js +13 -8
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/schema/mark-flags.d.ts +8 -1
- package/dist/core/schema/of-features.d.ts +13 -0
- package/dist/core/schema/of-features.js +14 -0
- package/dist/core/schema/of-features.js.map +1 -0
- package/dist/core/slash.d.ts +8 -6
- package/dist/core/slash.js +24 -16
- package/dist/core/slash.js.map +1 -1
- package/dist/core/surface-session.js +13 -5
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/toolbar-items.d.ts +10 -4
- package/dist/core/toolbar-items.js +269 -195
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/core/util.js +11 -0
- package/dist/core/util.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +1 -1
- package/dist/features/ai/agent-op-review.js +1 -1
- package/dist/features/ai/agent-ops.js +1 -1
- package/dist/features/ai/ai-apply.d.ts +2 -2
- package/dist/features/ai/ai-apply.js +1 -1
- package/dist/features/ai/ai-review.js +1 -1
- package/dist/features/ai/ai-sse.d.ts +6 -6
- package/dist/features/ai/ai-sse.js +20 -63
- package/dist/features/ai/ai-sse.js.map +1 -1
- package/dist/features/ai/assistant.d.ts +8 -7
- package/dist/features/ai/assistant.js +3 -3
- package/dist/features/ai/assistant.js.map +1 -1
- package/dist/features/ai/block-actions.d.ts +3 -2
- package/dist/features/ai/block-actions.js +3 -2
- package/dist/features/ai/block-actions.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.vue.d.ts +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +8 -8
- package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/document-chat-model.d.ts +8 -7
- package/dist/features/ai/document-chat-model.js +5 -5
- package/dist/features/ai/document-chat-model.js.map +1 -1
- package/dist/features/ai/document-chat.js +1 -1
- package/dist/features/ai/feature.js +27 -14
- package/dist/features/ai/feature.js.map +1 -1
- package/dist/features/ai/ghost-autocomplete.d.ts +7 -6
- package/dist/features/ai/ghost-autocomplete.js +6 -9
- package/dist/features/ai/ghost-autocomplete.js.map +1 -1
- package/dist/features/ai/index.d.ts +4 -3
- package/dist/features/ai/index.js +10 -10
- package/dist/features/ai/model-profiles.d.ts +5 -4
- package/dist/features/ai/model-profiles.js +2 -2
- package/dist/features/ai/model-profiles.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.js +1 -1
- package/dist/features/ai/protocol/block-lines.js.map +1 -1
- package/dist/features/ai/protocol/chat.js +4 -1
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/models.d.ts +2 -16
- package/dist/features/ai/protocol/models.js.map +1 -1
- package/dist/features/ai/rate-limit.d.ts +1 -1
- package/dist/features/ai/rate-limit.js +3 -1
- package/dist/features/ai/rate-limit.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js +2 -2
- package/dist/features/ai/react/AiSidebar.js +4 -4
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.d.ts +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +8 -10
- package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
- package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/react/use-ai-assistant.js +3 -3
- package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/react/use-document-chat.js +2 -2
- package/dist/features/ai/react/use-document-chat.js.map +1 -1
- package/dist/features/ai/react/use-model-profiles.d.ts +2 -1
- package/dist/features/ai/selection-flow.d.ts +1 -1
- package/dist/features/ai/selection-flow.js +1 -1
- package/dist/features/ai/sidebar-flow.d.ts +1 -1
- package/dist/features/ai/sidebar-flow.js +1 -1
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-ai-assistant.js +3 -3
- package/dist/features/ai/use-ai-assistant.js.map +1 -1
- package/dist/features/ai/use-document-chat.js +2 -2
- package/dist/features/ai/use-document-chat.js.map +1 -1
- package/dist/features/ai/use-model-profiles.d.ts +2 -1
- package/dist/features/authors/attribution.d.ts +1 -1
- package/dist/features/authors/feature.js +12 -11
- package/dist/features/authors/feature.js.map +1 -1
- package/dist/features/block-ui/block-actions.d.ts +22 -1
- package/dist/features/block-ui/block-actions.js +26 -2
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.js +33 -4
- package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +4 -0
- package/dist/features/block-ui/components/BlockUi.js +1 -1
- package/dist/features/block-ui/feature.js +1 -0
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/index.d.ts +2 -2
- package/dist/features/block-ui/index.js +2 -2
- package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +4 -0
- package/dist/features/block-ui/react/BlockActionsOverlay.js +38 -12
- package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.js +1 -1
- package/dist/features/collab/anchor-resolver.js +1 -1
- package/dist/features/collab/feature.js +2 -0
- package/dist/features/collab/feature.js.map +1 -1
- package/dist/features/comments/components/CommentThreadView.vue.d.ts +2 -1
- package/dist/features/comments/feature.js +12 -11
- package/dist/features/comments/feature.js.map +1 -1
- package/dist/features/comments/index.d.ts +3 -2
- package/dist/features/comments/index.js +1 -1
- package/dist/features/comments/margin-layout.d.ts +3 -2
- package/dist/features/comments/model.d.ts +3 -67
- package/dist/features/comments/model.js +2 -2
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentThreadView.d.ts +3 -2
- package/dist/features/comments/react/use-comments.d.ts +2 -1
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +2 -52
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js.map +1 -1
- package/dist/features/comments/use-comments.d.ts +3 -2
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +1 -0
- package/dist/features/composites/accepted-children.js +16 -1
- package/dist/features/composites/accepted-children.js.map +1 -1
- package/dist/features/composites/ai-brief.js +31 -0
- package/dist/features/composites/ai-brief.js.map +1 -1
- package/dist/features/composites/bibliography-command.d.ts +11 -0
- package/dist/features/composites/bibliography-form.js +90 -0
- package/dist/features/composites/bibliography-form.js.map +1 -0
- package/dist/features/composites/bibliography.d.ts +33 -0
- package/dist/features/composites/bibliography.js +120 -0
- package/dist/features/composites/bibliography.js.map +1 -0
- package/dist/features/composites/components/BibliographyEditor.js +261 -0
- package/dist/features/composites/components/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/components/CompositeChrome.js +75 -26
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.js +46 -11
- package/dist/features/composites/components/CompositeDetails.js.map +1 -1
- package/dist/features/composites/components/CompositeDetails.vue.d.ts +2 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +37 -4
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeInspector.js +17 -5
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/components/CompositeTable.js +13 -4
- package/dist/features/composites/components/CompositeTable.js.map +1 -1
- package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
- package/dist/features/composites/components/CompositeView.js +11 -1
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +103 -0
- package/dist/features/composites/components/DerivationBadge.js.map +1 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js +60 -0
- package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.js +122 -0
- package/dist/features/composites/components/ReferenceView.js.map +1 -0
- package/dist/features/composites/components/ReferenceView.vue.d.ts +96 -0
- package/dist/features/composites/components/SettingsEditingTab.js +1 -1
- package/dist/features/composites/convert.js +2 -2
- package/dist/features/composites/convert.js.map +1 -1
- package/dist/features/composites/derivation.d.ts +61 -0
- package/dist/features/composites/derivation.js +98 -0
- package/dist/features/composites/derivation.js.map +1 -0
- package/dist/features/composites/derived-chrome.js +69 -0
- package/dist/features/composites/derived-chrome.js.map +1 -0
- package/dist/features/composites/feature.js +159 -109
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/fences.d.ts +4 -3
- package/dist/features/composites/fences.js +51 -7
- package/dist/features/composites/fences.js.map +1 -1
- package/dist/features/composites/index.d.ts +12 -3
- package/dist/features/composites/index.js +15 -6
- package/dist/features/composites/inspector-model.d.ts +4 -1
- package/dist/features/composites/inspector-model.js +8 -4
- package/dist/features/composites/inspector-model.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +8 -1
- package/dist/features/composites/kinds.js +145 -6
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/legacy.d.ts +4 -1
- package/dist/features/composites/legacy.js +56 -1
- package/dist/features/composites/legacy.js.map +1 -1
- package/dist/features/composites/meta-display.js +40 -1
- package/dist/features/composites/meta-display.js.map +1 -1
- package/dist/features/composites/meta-keys.d.ts +7 -0
- package/dist/features/composites/meta-keys.js +21 -0
- package/dist/features/composites/meta-keys.js.map +1 -0
- package/dist/features/composites/node-views.d.ts +6 -3
- package/dist/features/composites/node-views.js +7 -3
- package/dist/features/composites/node-views.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +6 -1
- package/dist/features/composites/nodes.js +33 -7
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/react/BibliographyEditor.js +229 -0
- package/dist/features/composites/react/BibliographyEditor.js.map +1 -0
- package/dist/features/composites/react/CompositeChrome.js +65 -17
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.d.ts +2 -1
- package/dist/features/composites/react/CompositeDetails.js +45 -6
- package/dist/features/composites/react/CompositeDetails.js.map +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.d.ts +1 -1
- package/dist/features/composites/react/CompositeFieldEditor.js +24 -1
- package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/react/CompositeInspector.js +11 -5
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.js +1 -1
- package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
- package/dist/features/composites/react/CompositeTable.js +16 -2
- package/dist/features/composites/react/CompositeTable.js.map +1 -1
- package/dist/features/composites/react/CompositeTagsEditor.js +2 -2
- package/dist/features/composites/react/CompositeView.js +6 -1
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +116 -0
- package/dist/features/composites/react/DerivationBadge.js.map +1 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js +52 -0
- package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -0
- package/dist/features/composites/react/ReferenceView.d.ts +11 -0
- package/dist/features/composites/react/ReferenceView.js +115 -0
- package/dist/features/composites/react/ReferenceView.js.map +1 -0
- package/dist/features/composites/react/SettingsEditingTab.js +2 -2
- package/dist/features/composites/react.d.ts +2 -1
- package/dist/features/composites/react.js +7 -3
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/reference-ai.js +16 -0
- package/dist/features/composites/reference-ai.js.map +1 -0
- package/dist/features/composites/reference-label.d.ts +19 -0
- package/dist/features/composites/reference-label.js +58 -0
- package/dist/features/composites/reference-label.js.map +1 -0
- package/dist/features/composites/reference-suggestion.d.ts +10 -0
- package/dist/features/composites/reference-suggestion.js +41 -0
- package/dist/features/composites/reference-suggestion.js.map +1 -0
- package/dist/features/composites/reference-text.d.ts +18 -0
- package/dist/features/composites/reference-text.js +89 -0
- package/dist/features/composites/reference-text.js.map +1 -0
- package/dist/features/composites/reference.d.ts +44 -0
- package/dist/features/composites/reference.js +148 -0
- package/dist/features/composites/reference.js.map +1 -0
- package/dist/features/composites/reveal-source.d.ts +32 -0
- package/dist/features/composites/reveal-source.js +89 -0
- package/dist/features/composites/reveal-source.js.map +1 -0
- package/dist/features/composites/rows.d.ts +21 -2
- package/dist/features/composites/rows.js +63 -9
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/composites/schema.d.ts +9 -3
- package/dist/features/composites/schema.js +11 -4
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/table-model.js +12 -21
- package/dist/features/composites/table-model.js.map +1 -1
- package/dist/features/composites/vue.d.ts +3 -2
- package/dist/features/composites/vue.js +8 -4
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/media/assets.d.ts +2 -0
- package/dist/features/media/assets.js +3 -2
- package/dist/features/media/assets.js.map +1 -1
- package/dist/features/media/components/FileView.js +13 -4
- package/dist/features/media/components/FileView.js.map +1 -1
- package/dist/features/media/feature.d.ts +3 -3
- package/dist/features/media/feature.js +21 -28
- package/dist/features/media/feature.js.map +1 -1
- package/dist/features/media/file-import.js +2 -2
- package/dist/features/media/file-import.js.map +1 -1
- package/dist/features/media/index.d.ts +2 -2
- package/dist/features/media/index.js +2 -2
- package/dist/features/media/react/FileView.js +19 -2
- package/dist/features/media/react/FileView.js.map +1 -1
- package/dist/features/media/schema.d.ts +8 -0
- package/dist/features/media/schema.js +126 -11
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/media/upload.d.ts +16 -14
- package/dist/features/media/upload.js +13 -19
- package/dist/features/media/upload.js.map +1 -1
- package/dist/features/mentions/index.d.ts +4 -3
- package/dist/features/mentions/index.js +4 -1
- package/dist/features/mentions/index.js.map +1 -1
- package/dist/features/mentions/mention-text.d.ts +1 -1
- package/dist/features/mentions/mention-text.js.map +1 -1
- package/dist/features/mentions/mention.d.ts +0 -6
- package/dist/features/mentions/mention.js.map +1 -1
- package/dist/features/mentions/metadata-defaults.d.ts +2 -2
- package/dist/features/mentions/metadata-defaults.js +3 -9
- package/dist/features/mentions/metadata-defaults.js.map +1 -1
- package/dist/features/mentions/schema.d.ts +2 -1
- package/dist/features/mentions/schema.js.map +1 -1
- package/dist/features/tables/feature.d.ts +2 -1
- package/dist/features/tables/feature.js +90 -70
- package/dist/features/tables/feature.js.map +1 -1
- package/dist/features/themes/components/SettingsAppearanceTab.js +6 -9
- package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/feature.js +2 -0
- package/dist/features/themes/feature.js.map +1 -1
- package/dist/features/themes/index.d.ts +2 -1
- package/dist/features/themes/react/SettingsAppearanceTab.js +7 -10
- package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
- package/dist/features/themes/schema.d.ts +2 -6
- package/dist/features/themes/schema.js.map +1 -1
- package/dist/features/themes/theme-runtime.d.ts +1 -1
- package/dist/features/themes/theme-runtime.js +4 -2
- package/dist/features/themes/theme-runtime.js.map +1 -1
- package/dist/features/transcript/TranscriptSegmentView.js +48 -28
- package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/feature.js +21 -19
- package/dist/features/transcript/feature.js.map +1 -1
- package/dist/features/transcript/react/TranscriptSegmentView.js +55 -36
- package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
- package/dist/features/transcript/segment-node.d.ts +2 -0
- package/dist/features/transcript/segment-node.js +35 -7
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/composites.js +91 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/integrations.js +49 -8
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/media.js +0 -1
- package/dist/i18n/de/media.js.map +1 -1
- package/dist/i18n/de/palette.js +22 -0
- package/dist/i18n/de/palette.js.map +1 -0
- package/dist/i18n/de/tables.js +0 -1
- package/dist/i18n/de/tables.js.map +1 -1
- package/dist/i18n/de/transcript.js +2 -1
- package/dist/i18n/de/transcript.js.map +1 -1
- package/dist/i18n/de/tree.js +3 -0
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +157 -11
- package/dist/i18n/de.js +3 -1
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/composites.js +89 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +49 -8
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/media.js +0 -1
- package/dist/i18n/en/media.js.map +1 -1
- package/dist/i18n/en/palette.js +22 -0
- package/dist/i18n/en/palette.js.map +1 -0
- package/dist/i18n/en/tables.js +0 -1
- package/dist/i18n/en/tables.js.map +1 -1
- package/dist/i18n/en/transcript.js +2 -1
- package/dist/i18n/en/transcript.js.map +1 -1
- package/dist/i18n/en/tree.js +3 -0
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/integrations/composite-ai/bibliography-fetch.d.ts +31 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js +159 -0
- package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -0
- package/dist/integrations/composite-ai/composite-fill.d.ts +30 -8
- package/dist/integrations/composite-ai/composite-fill.js +97 -56
- package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
- package/dist/integrations/composite-ai/composite-summary.d.ts +4 -12
- package/dist/integrations/composite-ai/composite-summary.js +7 -26
- package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +29 -0
- package/dist/integrations/composite-ai/derive.js +173 -0
- package/dist/integrations/composite-ai/derive.js.map +1 -0
- package/dist/integrations/composite-ai/index.d.ts +11 -7
- package/dist/integrations/composite-ai/index.js +72 -14
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/rendition.d.ts +45 -0
- package/dist/integrations/composite-ai/rendition.js +314 -0
- package/dist/integrations/composite-ai/rendition.js.map +1 -0
- package/dist/integrations/composite-media/index.d.ts +7 -5
- package/dist/integrations/composite-media/index.js +86 -13
- package/dist/integrations/composite-media/index.js.map +1 -1
- package/dist/integrations/transcript-ai/index.d.ts +1 -1
- package/dist/integrations/transcript-ai/index.js +5 -2
- package/dist/integrations/transcript-ai/index.js.map +1 -1
- package/dist/integrations/transcript-ai/protocol.d.ts +2 -12
- package/dist/integrations/transcript-ai/protocol.js +0 -2
- package/dist/integrations/transcript-ai/protocol.js.map +1 -1
- package/dist/integrations/transcript-ai/transcript-cleanup.d.ts +5 -5
- package/dist/integrations/transcript-ai/transcript-cleanup.js +7 -10
- package/dist/integrations/transcript-ai/transcript-cleanup.js.map +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +10 -9
- package/dist/markdown/MarkdownSurface2.js +3 -6
- package/dist/markdown/MarkdownSurface2.js.map +1 -1
- package/dist/markdown/document.js +1 -1
- package/dist/markdown/source-mode.d.ts +1 -1
- package/dist/nuxt/module.d.ts +1402 -54
- package/dist/nuxt/module.js +6 -49
- package/dist/nuxt/module.js.map +1 -1
- package/dist/nuxt/runtime/install-context.d.ts +7 -3
- package/dist/nuxt/runtime/install-context.js +14 -8
- package/dist/nuxt/runtime/install-context.js.map +1 -1
- package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +2 -2
- package/dist/nuxt/runtime/server/plugins/editor-server.js +20 -12
- package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
- package/dist/presets/compose.d.ts +94 -0
- package/dist/presets/compose.js +164 -0
- package/dist/presets/compose.js.map +1 -0
- package/dist/react/AdvancedEditor.d.ts +15 -15
- package/dist/react/AdvancedEditor.js +15 -13
- package/dist/react/AdvancedEditor.js.map +1 -1
- package/dist/react/EditorCommandPalette.d.ts +13 -0
- package/dist/react/EditorCommandPalette.js +118 -0
- package/dist/react/EditorCommandPalette.js.map +1 -0
- package/dist/react/EditorSettingsModal.d.ts +5 -5
- package/dist/react/EditorSettingsModal.js +7 -4
- package/dist/react/EditorSettingsModal.js.map +1 -1
- package/dist/react/EditorSettingsPanel.d.ts +7 -7
- package/dist/react/EditorSettingsPanel.js +8 -5
- package/dist/react/EditorSettingsPanel.js.map +1 -1
- package/dist/react/EditorSurface.d.ts +7 -7
- package/dist/react/EditorSurface.js +10 -6
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.d.ts +4 -6
- package/dist/react/EditorToolbar.js +29 -37
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/react/SimpleEditor.d.ts +22 -23
- package/dist/react/SimpleEditor.js +21 -7
- package/dist/react/SimpleEditor.js.map +1 -1
- package/dist/react/advanced.d.ts +4 -3
- package/dist/react/advanced.js +2 -2
- package/dist/react/compose.d.ts +20 -0
- package/dist/react/compose.js +49 -0
- package/dist/react/compose.js.map +1 -0
- package/dist/react/composed-surface.d.ts +39 -0
- package/dist/react/composed-surface.js +71 -0
- package/dist/react/composed-surface.js.map +1 -0
- package/dist/react/context.js +6 -2
- package/dist/react/context.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/key-labels.js +15 -0
- package/dist/react/key-labels.js.map +1 -0
- package/dist/react/settings.d.ts +1 -1
- package/dist/react/simple.d.ts +2 -2
- package/dist/react/ui/command.js +36 -1
- package/dist/react/ui/command.js.map +1 -1
- package/dist/schema/advanced.d.ts +6 -6
- package/dist/schema/advanced.js +26 -29
- package/dist/schema/advanced.js.map +1 -1
- package/dist/server/ai.d.ts +5 -16
- package/dist/server/ai.js.map +1 -1
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/collab-state.d.ts +1 -1
- package/dist/server/collab-state.js.map +1 -1
- package/dist/server/collab.d.ts +13 -13
- package/dist/server/collab.js +34 -18
- package/dist/server/collab.js.map +1 -1
- package/dist/server/crossref.js +78 -0
- package/dist/server/crossref.js.map +1 -0
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/errors.js +1 -1
- package/dist/server/errors.js.map +1 -1
- package/dist/server/extract.js +308 -0
- package/dist/server/extract.js.map +1 -0
- package/dist/{nuxt/runtime/server/utils/editor-route.js → server/h3.js} +8 -16
- package/dist/server/h3.js.map +1 -0
- package/dist/server/handlers/ai.d.ts +30 -9
- package/dist/server/handlers/ai.js +170 -36
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +12 -5
- package/dist/server/handlers/context.js +10 -4
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +19 -13
- package/dist/server/handlers/editor.js +49 -38
- package/dist/server/handlers/editor.js.map +1 -1
- package/dist/server/handlers/index.d.ts +4 -4
- package/dist/server/handlers/index.js +4 -4
- package/dist/server/host.d.ts +16 -25
- package/dist/server/host.js +58 -41
- package/dist/server/host.js.map +1 -1
- package/dist/server/index.d.ts +6 -4
- package/dist/server/index.js +3 -2
- package/dist/server/pdf.js +97 -0
- package/dist/server/pdf.js.map +1 -0
- package/dist/server/{adapter.d.ts → ports.d.ts} +81 -30
- package/dist/server/ports.js +29 -0
- package/dist/server/ports.js.map +1 -0
- package/dist/server/routes.js +144 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js.map +1 -1
- package/dist/styles/composites.css +25 -0
- package/dist/tree/DocumentTree.js +56 -29
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +23 -0
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +1 -1
- package/dist/vue/AdvancedEditor.js +30 -9
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +32 -12
- package/dist/vue/SimpleEditor.js +62 -16
- package/dist/vue/SimpleEditor.js.map +1 -1
- package/dist/vue/SimpleEditor.vue.d.ts +37 -7
- package/dist/vue/advanced.d.ts +3 -2
- package/dist/vue/advanced.js +2 -2
- package/dist/vue/components/EditorCommandPalette.js +91 -0
- package/dist/vue/components/EditorCommandPalette.js.map +1 -0
- package/dist/vue/components/EditorCommandPalette.vue.d.ts +15 -0
- package/dist/vue/components/EditorSettingsModal.js +10 -6
- package/dist/vue/components/EditorSettingsModal.js.map +1 -1
- package/dist/vue/components/EditorSettingsModal.vue.d.ts +4 -4
- package/dist/vue/components/EditorSettingsPanel.js +10 -6
- package/dist/vue/components/EditorSettingsPanel.js.map +1 -1
- package/dist/vue/components/EditorSettingsPanel.vue.d.ts +6 -6
- package/dist/vue/components/EditorSurface.js +11 -6
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +11 -11
- package/dist/vue/components/EditorToolbar.js +41 -49
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/components/EditorToolbar.vue.d.ts +2 -4
- package/dist/vue/compose.d.ts +17 -0
- package/dist/vue/compose.js +51 -0
- package/dist/vue/compose.js.map +1 -0
- package/dist/vue/context-key.d.ts +6 -0
- package/dist/vue/context-key.js +6 -0
- package/dist/vue/context-key.js.map +1 -0
- package/dist/vue/context.d.ts +8 -6
- package/dist/vue/context.js +6 -5
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/index.d.ts +10 -8
- package/dist/vue/index.js +10 -8
- package/dist/vue/kit/index.d.ts +2 -2
- package/dist/vue/kit/nuxt-ui.js +23 -0
- package/dist/vue/kit/nuxt-ui.js.map +1 -1
- package/dist/vue/kit/types.d.ts +40 -1
- package/package.json +30 -16
- package/dist/adapters/comments/client.d.ts +0 -64
- package/dist/adapters/comments/client.js.map +0 -1
- package/dist/adapters/routes.js.map +0 -1
- package/dist/core/toolbar.d.ts +0 -74
- package/dist/core/toolbar.js +0 -45
- package/dist/core/toolbar.js.map +0 -1
- package/dist/integrations/composite-ai/document-transcribe.d.ts +0 -14
- package/dist/integrations/composite-ai/document-transcribe.js +0 -89
- package/dist/integrations/composite-ai/document-transcribe.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/chat.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/chat.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/chat.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/complete.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/complete.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/complete.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/generate.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/generate.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/generate.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/models.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/models.get.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/models.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcribe.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js +0 -8
- package/dist/nuxt/runtime/server/api/ai/transcript-cleanup.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id]/[name].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/assets/[id].post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/settings.put.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/settings.put.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/settings.put.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/tags.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/tags.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/tags.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].delete.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/[id].get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.get.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/themes/index.post.js.map +0 -1
- package/dist/nuxt/runtime/server/api/editor/users.get.d.ts +0 -5
- package/dist/nuxt/runtime/server/api/editor/users.get.js +0 -8
- package/dist/nuxt/runtime/server/api/editor/users.get.js.map +0 -1
- package/dist/nuxt/runtime/server/plugins/collab.d.ts +0 -5
- package/dist/nuxt/runtime/server/plugins/collab.js +0 -10
- package/dist/nuxt/runtime/server/plugins/collab.js.map +0 -1
- package/dist/nuxt/runtime/server/routes/collab/[id].d.ts +0 -5
- package/dist/nuxt/runtime/server/routes/collab/[id].js +0 -35
- package/dist/nuxt/runtime/server/routes/collab/[id].js.map +0 -1
- package/dist/nuxt/runtime/server/utils/editor-route.d.ts +0 -11
- package/dist/nuxt/runtime/server/utils/editor-route.js.map +0 -1
- package/dist/presets/advanced.d.ts +0 -33
- package/dist/presets/advanced.js +0 -50
- package/dist/presets/advanced.js.map +0 -1
- package/dist/presets/simple.d.ts +0 -9
- package/dist/presets/simple.js +0 -12
- package/dist/presets/simple.js.map +0 -1
- package/dist/react/advanced-preset.d.ts +0 -11
- package/dist/react/advanced-preset.js +0 -31
- package/dist/react/advanced-preset.js.map +0 -1
- package/dist/vue/advanced-preset.d.ts +0 -13
- package/dist/vue/advanced-preset.js +0 -33
- package/dist/vue/advanced-preset.js.map +0 -1
- package/docs/extending.md +0 -538
- package/docs/integration.md +0 -630
- package/docs/llms.md +0 -383
- package/docs/usage.md +0 -1076
|
@@ -8,9 +8,9 @@ export declare const chatBodySchema: z.ZodObject<{
|
|
|
8
8
|
feedback: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
9
9
|
op: z.ZodString;
|
|
10
10
|
type: z.ZodEnum<{
|
|
11
|
+
add: "add";
|
|
11
12
|
update: "update";
|
|
12
13
|
delete: "delete";
|
|
13
|
-
add: "add";
|
|
14
14
|
convert: "convert";
|
|
15
15
|
}>;
|
|
16
16
|
target: z.ZodNullable<z.ZodString>;
|
|
@@ -186,21 +186,42 @@ export declare const generateBodySchema: z.ZodObject<{
|
|
|
186
186
|
/** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */
|
|
187
187
|
export declare function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
188
188
|
/**
|
|
189
|
-
* The models the client may choose from (without keys) — the
|
|
189
|
+
* The models the client may choose from (without keys) — the `models` port's `listModels` for the user, else the
|
|
190
190
|
* server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an
|
|
191
|
-
* authenticated user
|
|
191
|
+
* authenticated user through the `identity` port; not rate limited, since it calls no model.
|
|
192
192
|
*/
|
|
193
193
|
export declare function modelsGet(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
194
|
-
/** Body schema of `
|
|
195
|
-
export declare const
|
|
194
|
+
/** Body schema of `extractPost`. */
|
|
195
|
+
export declare const extractBodySchema: z.ZodObject<{
|
|
196
196
|
documentId: z.ZodString;
|
|
197
|
-
name: z.ZodString
|
|
197
|
+
name: z.ZodOptional<z.ZodString>;
|
|
198
|
+
method: z.ZodEnum<{
|
|
199
|
+
transcription: "transcription";
|
|
200
|
+
description: "description";
|
|
201
|
+
bibliography: "bibliography";
|
|
202
|
+
auto: "auto";
|
|
203
|
+
"pdf-text": "pdf-text";
|
|
204
|
+
ocr: "ocr";
|
|
205
|
+
}>;
|
|
206
|
+
model: z.ZodUnion<readonly [z.ZodObject<{
|
|
207
|
+
preset: z.ZodString;
|
|
208
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
209
|
+
endpoint: z.ZodString;
|
|
210
|
+
model: z.ZodString;
|
|
211
|
+
apiKey: z.ZodString;
|
|
212
|
+
}, z.core.$strip>]>;
|
|
213
|
+
bibliography: z.ZodOptional<z.ZodBoolean>;
|
|
214
|
+
doi: z.ZodOptional<z.ZodString>;
|
|
215
|
+
maxPages: z.ZodDefault<z.ZodNumber>;
|
|
198
216
|
}, z.core.$strip>;
|
|
199
217
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
218
|
+
* Turns a stored asset into readable content (`ExtractRequest` → `ExtractResponse`): audio/video into a transcription
|
|
219
|
+
* with segment timestamps, a PDF into its text per page (OCR through the model when it has no text layer), an image
|
|
220
|
+
* into a description, bibliographic data from Crossref or the model. The raw Markdown is stored as an asset of the same
|
|
221
|
+
* document (`output`), with the SHA-256 of input and output. Errors: 404 unknown asset, 415 type not supported, 413
|
|
222
|
+
* oversize audio, 422 unreadable PDF, 501 transcription or OCR unavailable, 502 upstream failure.
|
|
202
223
|
*/
|
|
203
|
-
export declare function
|
|
224
|
+
export declare function extractPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
204
225
|
/** Body schema of `transcriptCleanupPost`. */
|
|
205
226
|
export declare const transcriptCleanupBodySchema: z.ZodObject<{
|
|
206
227
|
segments: z.ZodArray<z.ZodObject<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AI_LANGUAGES, STRUCTURE_MODES } from "../../settings/values.js";
|
|
2
2
|
import { HttpError } from "../errors.js";
|
|
3
3
|
import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
|
|
4
|
-
import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
|
|
4
|
+
import { authenticated, enforceRateLimit, eventStream, json, port, readBody } from "./context.js";
|
|
5
5
|
import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
|
|
6
6
|
import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
|
|
7
7
|
import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
|
|
@@ -10,6 +10,10 @@ import { modelRefSchema } from "../../features/ai/protocol/models.js";
|
|
|
10
10
|
import { AI_LIMITS, AI_TASKS, AI_TASK_IDS } from "../../features/ai/protocol/tasks.js";
|
|
11
11
|
import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
|
|
12
12
|
import { TRANSCRIPT_CLEANUP_LIMITS, parseTranscriptCleanup, transcriptCleanupChunks, transcriptCleanupMessages, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
|
|
13
|
+
import { DOI_PATTERN } from "../crossref.js";
|
|
14
|
+
import { MAX_TRANSCRIBE_BYTES, RasterError, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, ocrPages, pdfPagesText, transcribe } from "../extract.js";
|
|
15
|
+
import { openPdf } from "../pdf.js";
|
|
16
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
13
17
|
import { z } from "zod";
|
|
14
18
|
import { jsonSchema, tool } from "ai";
|
|
15
19
|
//#region src/server/handlers/ai.ts
|
|
@@ -302,7 +306,7 @@ async function generatePost(ctx, request) {
|
|
|
302
306
|
if (task.needsBlock && !input.block) throw new HttpError(400, "Baustein fehlt");
|
|
303
307
|
if (task.wholeDocument && !input.context.trim()) throw new HttpError(400, "Dokument ist leer");
|
|
304
308
|
if (!task.wholeDocument && input.context.length > AI_LIMITS.context) throw new HttpError(400, "Kontext zu lang");
|
|
305
|
-
const users = task.needsUsers ? (await ctx.
|
|
309
|
+
const users = task.needsUsers ? (await port(ctx, "directory").listUsers()).map((u) => u.username) : void 0;
|
|
306
310
|
return eventStream(request, async (emit, signal) => {
|
|
307
311
|
try {
|
|
308
312
|
for await (const text of ctx.ai.streamChat({
|
|
@@ -323,9 +327,9 @@ async function generatePost(ctx, request) {
|
|
|
323
327
|
});
|
|
324
328
|
}
|
|
325
329
|
/**
|
|
326
|
-
* The models the client may choose from (without keys) — the
|
|
330
|
+
* The models the client may choose from (without keys) — the `models` port's `listModels` for the user, else the
|
|
327
331
|
* server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an
|
|
328
|
-
* authenticated user
|
|
332
|
+
* authenticated user through the `identity` port; not rate limited, since it calls no model.
|
|
329
333
|
*/
|
|
330
334
|
async function modelsGet(ctx, request) {
|
|
331
335
|
const user = await authenticated(ctx, request);
|
|
@@ -334,44 +338,174 @@ async function modelsGet(ctx, request) {
|
|
|
334
338
|
slots: ctx.ai.slots
|
|
335
339
|
});
|
|
336
340
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const transcribeBodySchema = z.object({
|
|
341
|
+
const sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
|
|
342
|
+
/** Body schema of `extractPost`. */
|
|
343
|
+
const extractBodySchema = z.object({
|
|
341
344
|
documentId: z.string().regex(ASSET_DOCUMENT_ID),
|
|
342
|
-
name: z.string().regex(ASSET_NAME)
|
|
345
|
+
name: z.string().regex(ASSET_NAME).optional(),
|
|
346
|
+
method: z.enum([
|
|
347
|
+
"auto",
|
|
348
|
+
"transcription",
|
|
349
|
+
"pdf-text",
|
|
350
|
+
"ocr",
|
|
351
|
+
"description",
|
|
352
|
+
"bibliography"
|
|
353
|
+
]),
|
|
354
|
+
model: modelRefSchema,
|
|
355
|
+
bibliography: z.boolean().optional(),
|
|
356
|
+
doi: z.string().regex(DOI_PATTERN).optional(),
|
|
357
|
+
maxPages: z.number().int().min(1).max(500).default(200)
|
|
358
|
+
}).refine((body) => body.name !== void 0 || body.method === "bibliography" && body.doi !== void 0, {
|
|
359
|
+
message: "name fehlt (nur eine Literaturabfrage mit DOI braucht keine Datei)",
|
|
360
|
+
path: ["name"]
|
|
343
361
|
});
|
|
362
|
+
/** The method an `ExtractRequest` runs for an asset of `mimeType`: `auto` picks by type; 415 when the type does not fit the method. */
|
|
363
|
+
function extractMethodOf(requested, mimeType, hasDoi) {
|
|
364
|
+
const audioVideo = /^(?:audio|video)\//.test(mimeType);
|
|
365
|
+
const image = mimeType.startsWith("image/");
|
|
366
|
+
const pdf = mimeType === "application/pdf";
|
|
367
|
+
if (!{
|
|
368
|
+
"auto": audioVideo || image || pdf,
|
|
369
|
+
"transcription": audioVideo,
|
|
370
|
+
"description": image,
|
|
371
|
+
"pdf-text": pdf,
|
|
372
|
+
"ocr": pdf,
|
|
373
|
+
"bibliography": pdf || hasDoi
|
|
374
|
+
}[requested]) throw new HttpError(415, `Dateityp ${mimeType} wird für diese Auswertung nicht unterstützt`);
|
|
375
|
+
if (requested !== "auto") return requested;
|
|
376
|
+
if (audioVideo) return "transcription";
|
|
377
|
+
return image ? "description" : "pdf-text";
|
|
378
|
+
}
|
|
344
379
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
380
|
+
* Turns a stored asset into readable content (`ExtractRequest` → `ExtractResponse`): audio/video into a transcription
|
|
381
|
+
* with segment timestamps, a PDF into its text per page (OCR through the model when it has no text layer), an image
|
|
382
|
+
* into a description, bibliographic data from Crossref or the model. The raw Markdown is stored as an asset of the same
|
|
383
|
+
* document (`output`), with the SHA-256 of input and output. Errors: 404 unknown asset, 415 type not supported, 413
|
|
384
|
+
* oversize audio, 422 unreadable PDF, 501 transcription or OCR unavailable, 502 upstream failure.
|
|
347
385
|
*/
|
|
348
|
-
async function
|
|
349
|
-
await enforceRateLimit(ctx, request);
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
386
|
+
async function extractPost(ctx, request) {
|
|
387
|
+
const user = await enforceRateLimit(ctx, request);
|
|
388
|
+
const body = await readBody(request, extractBodySchema);
|
|
389
|
+
if (body.name === void 0) {
|
|
390
|
+
const { bib, warnings } = await extractBibliography({
|
|
391
|
+
doi: body.doi,
|
|
392
|
+
text: "",
|
|
393
|
+
resolveModel: () => ctx.ai.resolveModel(body.model, user),
|
|
394
|
+
signal: request.signal
|
|
395
|
+
});
|
|
396
|
+
return json({
|
|
397
|
+
method: "bibliography",
|
|
398
|
+
markdown: "",
|
|
399
|
+
bib,
|
|
400
|
+
inputHash: "",
|
|
401
|
+
outputHash: sha256Hex(""),
|
|
402
|
+
output: null,
|
|
403
|
+
warnings
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const assets = port(ctx, "assets");
|
|
407
|
+
const asset = await assets.loadAsset(body.documentId, body.name);
|
|
354
408
|
if (!asset) throw new HttpError(404, "Datei nicht gefunden");
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
let
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
409
|
+
const inputHash = sha256Hex(asset.data);
|
|
410
|
+
let method = extractMethodOf(body.method, asset.record.mimeType, body.doi !== void 0);
|
|
411
|
+
const { signal } = request;
|
|
412
|
+
const resolveModel = () => ctx.ai.resolveModel(body.model, user);
|
|
413
|
+
const warnings = [];
|
|
414
|
+
let markdown = "";
|
|
415
|
+
let model;
|
|
416
|
+
let segments;
|
|
417
|
+
let bib;
|
|
418
|
+
if (method === "transcription") {
|
|
419
|
+
const config = ctx.ai.transcription;
|
|
420
|
+
if (!config?.endpoint || !config.model) throw new HttpError(501, "Transkription nicht konfiguriert");
|
|
421
|
+
if (asset.data.byteLength > 26214400) throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`);
|
|
422
|
+
({markdown, segments} = await transcribe(config, asset, signal));
|
|
423
|
+
model = config.model;
|
|
424
|
+
} else if (method === "description") markdown = await describeImage(await resolveModel(), asset.data, asset.record.mimeType, signal);
|
|
425
|
+
else if (asset.record.mimeType !== "application/pdf") {
|
|
426
|
+
const found = await extractBibliography({
|
|
427
|
+
doi: body.doi,
|
|
428
|
+
text: "",
|
|
429
|
+
resolveModel,
|
|
430
|
+
signal
|
|
431
|
+
});
|
|
432
|
+
bib = found.bib;
|
|
433
|
+
warnings.push(...found.warnings);
|
|
434
|
+
} else {
|
|
435
|
+
const pdf = await openPdf(asset.data);
|
|
436
|
+
try {
|
|
437
|
+
if (method === "bibliography") {
|
|
438
|
+
const pages = await pdfPagesText(pdf, Math.min(pdf.pageCount, 2), signal);
|
|
439
|
+
const found = await extractBibliography({
|
|
440
|
+
doi: body.doi,
|
|
441
|
+
text: bibliographyText(pages),
|
|
442
|
+
resolveModel,
|
|
443
|
+
signal
|
|
444
|
+
});
|
|
445
|
+
bib = found.bib;
|
|
446
|
+
warnings.push(...found.warnings);
|
|
447
|
+
} else {
|
|
448
|
+
const count = Math.min(pdf.pageCount, body.maxPages);
|
|
449
|
+
if (pdf.pageCount > count) warnings.push("pages-truncated");
|
|
450
|
+
let pages = await pdfPagesText(pdf, count, signal);
|
|
451
|
+
if (body.method === "auto" && isScanned(pages)) method = "ocr";
|
|
452
|
+
if (method === "ocr") {
|
|
453
|
+
const ocrCount = Math.min(count, 50);
|
|
454
|
+
try {
|
|
455
|
+
pages = await ocrPages(pdf, ocrCount, resolveModel, signal);
|
|
456
|
+
if (ocrCount < count) warnings.push("ocr-truncated");
|
|
457
|
+
} catch (error) {
|
|
458
|
+
if (!(error instanceof RasterError)) throw error;
|
|
459
|
+
if (!pages.some(Boolean)) throw new HttpError(501, "OCR nicht verfügbar");
|
|
460
|
+
warnings.push("ocr-unavailable");
|
|
461
|
+
method = "pdf-text";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
markdown = joinPages(pages);
|
|
465
|
+
if (body.bibliography) try {
|
|
466
|
+
const found = await extractBibliography({
|
|
467
|
+
doi: body.doi,
|
|
468
|
+
text: bibliographyText(pages),
|
|
469
|
+
resolveModel,
|
|
470
|
+
signal
|
|
471
|
+
});
|
|
472
|
+
bib = found.bib;
|
|
473
|
+
warnings.push(...found.warnings);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (signal?.aborted) throw error;
|
|
476
|
+
warnings.push("bibliography-failed");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} finally {
|
|
480
|
+
await pdf.close();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const outputHash = sha256Hex(markdown);
|
|
484
|
+
let output = null;
|
|
485
|
+
if (method !== "bibliography" && markdown) {
|
|
486
|
+
const data = Buffer.from(markdown, "utf8");
|
|
487
|
+
output = `${randomUUID()}.md`;
|
|
488
|
+
await assets.storeAsset(body.documentId, {
|
|
489
|
+
name: output,
|
|
490
|
+
filename: `${asset.record.filename}.${method}.md`.slice(0, 255),
|
|
491
|
+
mimeType: "text/markdown",
|
|
492
|
+
size: data.byteLength,
|
|
493
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
494
|
+
sha256: outputHash,
|
|
495
|
+
data
|
|
369
496
|
});
|
|
370
|
-
} catch (err) {
|
|
371
|
-
throw new HttpError(502, `Transkriptions-Endpunkt: ${err instanceof Error ? err.message : String(err)}`);
|
|
372
497
|
}
|
|
373
|
-
|
|
374
|
-
|
|
498
|
+
return json({
|
|
499
|
+
method,
|
|
500
|
+
markdown,
|
|
501
|
+
...segments && { segments },
|
|
502
|
+
...bib && { bib },
|
|
503
|
+
...model && { model },
|
|
504
|
+
inputHash,
|
|
505
|
+
outputHash,
|
|
506
|
+
output,
|
|
507
|
+
warnings
|
|
508
|
+
});
|
|
375
509
|
}
|
|
376
510
|
/** Body schema of `transcriptCleanupPost`. */
|
|
377
511
|
const transcriptCleanupBodySchema = transcriptCleanupRequestSchema;
|
|
@@ -419,6 +553,6 @@ async function transcriptCleanupPost(ctx, request) {
|
|
|
419
553
|
});
|
|
420
554
|
}
|
|
421
555
|
//#endregion
|
|
422
|
-
export { chatBodySchema, chatPost, completeBodySchema, completePost,
|
|
556
|
+
export { chatBodySchema, chatPost, completeBodySchema, completePost, extractBodySchema, extractPost, generateBodySchema, generatePost, modelsGet, transcriptCleanupBodySchema, transcriptCleanupPost };
|
|
423
557
|
|
|
424
558
|
//# sourceMappingURL=ai.js.map
|
|
@@ -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 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
|
+
{"version":3,"file":"ai.js","names":[],"sources":["../../../src/server/handlers/ai.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { 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 { ExtractMethod, ExtractRequest, ExtractResponse } from '../../core/backend-types'\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 { BIBLIOGRAPHY_PAGES, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, MAX_OCR_PAGES, MAX_TRANSCRIBE_BYTES, ocrPages, pdfPagesText, RasterError, transcribe, type TranscriptionResult } from '../extract'\nimport { DOI_PATTERN } from '../crossref'\nimport { openPdf } from '../pdf'\nimport { type AiHandlerContext, authenticated, enforceRateLimit, eventStream, type HandlerRequest, type HandlerResponse, json, port, 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 port(ctx, 'directory').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 `models` port'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 through the `identity` port; 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\nconst sha256Hex = (data: string | Uint8Array): string => createHash('sha256').update(data).digest('hex')\n\n/** PDF pages read by `pdf-text` and `ocr` unless the request caps them lower. */\nconst DEFAULT_MAX_PAGES = 200\n\n/** Body schema of `extractPost`. */\nexport const extractBodySchema = z.object({\n documentId: z.string().regex(ASSET_DOCUMENT_ID),\n name: z.string().regex(ASSET_NAME).optional(),\n method: z.enum(['auto', 'transcription', 'pdf-text', 'ocr', 'description', 'bibliography']),\n model: modelRefSchema,\n bibliography: z.boolean().optional(),\n doi: z.string().regex(DOI_PATTERN).optional(),\n maxPages: z.number().int().min(1).max(500).default(DEFAULT_MAX_PAGES),\n}).refine(body => body.name !== undefined || (body.method === 'bibliography' && body.doi !== undefined), {\n message: 'name fehlt (nur eine Literaturabfrage mit DOI braucht keine Datei)',\n path: ['name'],\n}) satisfies z.ZodType<ExtractRequest>\n\n/** The method an `ExtractRequest` runs for an asset of `mimeType`: `auto` picks by type; 415 when the type does not fit the method. */\nfunction extractMethodOf(requested: ExtractMethod, mimeType: string, hasDoi: boolean): ExtractResponse['method'] {\n const audioVideo = /^(?:audio|video)\\//.test(mimeType)\n const image = mimeType.startsWith('image/')\n const pdf = mimeType === 'application/pdf'\n const fits: Record<ExtractMethod, boolean> = {\n 'auto': audioVideo || image || pdf,\n 'transcription': audioVideo,\n 'description': image,\n 'pdf-text': pdf,\n 'ocr': pdf,\n // A given DOI needs no document text, so any asset can stand for the work.\n 'bibliography': pdf || hasDoi,\n }\n if (!fits[requested]) throw new HttpError(415, `Dateityp ${mimeType} wird für diese Auswertung nicht unterstützt`)\n if (requested !== 'auto') return requested\n if (audioVideo) return 'transcription'\n return image ? 'description' : 'pdf-text'\n}\n\n/**\n * Turns a stored asset into readable content (`ExtractRequest` → `ExtractResponse`): audio/video into a transcription\n * with segment timestamps, a PDF into its text per page (OCR through the model when it has no text layer), an image\n * into a description, bibliographic data from Crossref or the model. The raw Markdown is stored as an asset of the same\n * document (`output`), with the SHA-256 of input and output. Errors: 404 unknown asset, 415 type not supported, 413\n * oversize audio, 422 unreadable PDF, 501 transcription or OCR unavailable, 502 upstream failure.\n */\nexport async function extractPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const body = await readBody(request, extractBodySchema)\n if (body.name === undefined) {\n // Only a DOI lookup comes without a file (the schema allows nothing else): no asset, no output, empty hashes.\n const { bib, warnings } = await extractBibliography({ doi: body.doi, text: '', resolveModel: () => ctx.ai.resolveModel(body.model, user), signal: request.signal })\n return json({ method: 'bibliography', markdown: '', bib, inputHash: '', outputHash: sha256Hex(''), output: null, warnings } satisfies ExtractResponse)\n }\n const assets = port(ctx, 'assets')\n const asset = await assets.loadAsset(body.documentId, body.name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n const inputHash = sha256Hex(asset.data)\n let method = extractMethodOf(body.method, asset.record.mimeType, body.doi !== undefined)\n // Closing the page aborts the client's fetch; the upstream requests stop with it.\n const { signal } = request\n const resolveModel = () => ctx.ai.resolveModel(body.model, user)\n\n const warnings: string[] = []\n let markdown = ''\n let model: string | undefined\n let segments: TranscriptionResult['segments']\n let bib: ExtractResponse['bib']\n\n if (method === 'transcription') {\n const config = ctx.ai.transcription\n if (!config?.endpoint || !config.model) throw new HttpError(501, 'Transkription nicht konfiguriert')\n if (asset.data.byteLength > MAX_TRANSCRIBE_BYTES) throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`)\n ;({ markdown, segments } = await transcribe(config, asset, signal))\n model = config.model\n }\n else if (method === 'description') {\n markdown = await describeImage(await resolveModel(), asset.data, asset.record.mimeType, signal)\n }\n else if (asset.record.mimeType !== 'application/pdf') {\n // `bibliography` of a non-PDF asset: only reachable with a DOI.\n const found = await extractBibliography({ doi: body.doi, text: '', resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const pdf = await openPdf(asset.data)\n try {\n if (method === 'bibliography') {\n const pages = await pdfPagesText(pdf, Math.min(pdf.pageCount, BIBLIOGRAPHY_PAGES), signal)\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const count = Math.min(pdf.pageCount, body.maxPages)\n if (pdf.pageCount > count) warnings.push('pages-truncated')\n let pages = await pdfPagesText(pdf, count, signal)\n if (body.method === 'auto' && isScanned(pages)) method = 'ocr'\n if (method === 'ocr') {\n // One vision call per page: the OCR reads fewer pages than the text layer would.\n const ocrCount = Math.min(count, MAX_OCR_PAGES)\n try {\n pages = await ocrPages(pdf, ocrCount, resolveModel, signal)\n if (ocrCount < count) warnings.push('ocr-truncated')\n }\n catch (error) {\n if (!(error instanceof RasterError)) throw error\n // Pages cannot be rendered here: keep what the text layer has.\n if (!pages.some(Boolean)) throw new HttpError(501, 'OCR nicht verfügbar')\n warnings.push('ocr-unavailable')\n method = 'pdf-text'\n }\n }\n markdown = joinPages(pages)\n if (body.bibliography) {\n try {\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n catch (error) {\n // The text is the result; missing bibliographic data must not lose it.\n if (signal?.aborted) throw error\n warnings.push('bibliography-failed')\n }\n }\n }\n }\n finally {\n await pdf.close()\n }\n }\n\n const outputHash = sha256Hex(markdown)\n let output: string | null = null\n if (method !== 'bibliography' && markdown) {\n const data = Buffer.from(markdown, 'utf8')\n output = `${randomUUID()}.md`\n await assets.storeAsset(body.documentId, {\n name: output,\n filename: `${asset.record.filename}.${method}.md`.slice(0, 255),\n mimeType: 'text/markdown',\n size: data.byteLength,\n createdAt: new Date().toISOString(),\n sha256: outputHash,\n data,\n })\n }\n return json({ method, markdown, ...(segments && { segments }), ...(bib && { bib }), ...(model && { model }), inputHash, outputHash, output, warnings } satisfies ExtractResponse)\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,13 +1,18 @@
|
|
|
1
|
-
import { EditorAdapter } from "../adapter.js";
|
|
2
1
|
import { AiModels } from "../ai.js";
|
|
2
|
+
import { EditorPorts } from "../ports.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
|
|
6
|
-
/** What every editor handler needs: the
|
|
6
|
+
/** What every editor handler needs: the ports of the backend (identity first). */
|
|
7
7
|
export interface HandlerContext {
|
|
8
|
-
|
|
8
|
+
ports: EditorPorts;
|
|
9
9
|
/** The collaboration server of the process: documents open in it count for `tagsGet` with their unsaved content. */
|
|
10
10
|
collab?: CollabServer;
|
|
11
|
+
/** What the installed server really serves (`capabilities` answers with it); without it, what `ports` imply. */
|
|
12
|
+
served?: {
|
|
13
|
+
ai?: boolean;
|
|
14
|
+
collaboration?: boolean;
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
/** Context of the AI handlers: the configured models and the per-user limit shared by all of them. */
|
|
13
18
|
export interface AiHandlerContext extends HandlerContext {
|
|
@@ -42,8 +47,10 @@ export declare const empty: (status?: number, headers?: Record<string, string>)
|
|
|
42
47
|
* client cancels the response or `request.signal` aborts. An `emit` after that is dropped.
|
|
43
48
|
*/
|
|
44
49
|
export declare function eventStream(request: HandlerRequest, run: (emit: (event: string, data: string) => void, signal: AbortSignal) => Promise<void>): HandlerResponse;
|
|
45
|
-
/** The user `
|
|
46
|
-
export declare function authenticated({
|
|
50
|
+
/** The user `ports.identity.authenticate` finds behind the request's headers; 401 without one. */
|
|
51
|
+
export declare function authenticated({ ports }: HandlerContext, request: HandlerRequest): Promise<object>;
|
|
52
|
+
/** The port `name` of the backend; 501 when the host serves none. The one place absent ports turn into an answer. */
|
|
53
|
+
export declare function port<K extends Exclude<keyof EditorPorts, 'identity' | 'documents'>>(ctx: HandlerContext, name: K): NonNullable<EditorPorts[K]>;
|
|
47
54
|
/** Per-user limit for the AI handlers: 401 without a user, 429 with `Retry-After` when exceeded; returns the user, for `resolveModel`. */
|
|
48
55
|
export declare function enforceRateLimit(ctx: AiHandlerContext, request: HandlerRequest): Promise<object>;
|
|
49
56
|
interface BodySchema<T> {
|
|
@@ -54,12 +54,18 @@ function eventStream(request, run) {
|
|
|
54
54
|
})
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
/** The user `
|
|
58
|
-
async function authenticated({
|
|
59
|
-
const user = await
|
|
57
|
+
/** The user `ports.identity.authenticate` finds behind the request's headers; 401 without one. */
|
|
58
|
+
async function authenticated({ ports }, request) {
|
|
59
|
+
const user = await ports.identity.authenticate({ headers: request.headers });
|
|
60
60
|
if (!user) throw new HttpError(401, "Nicht angemeldet");
|
|
61
61
|
return user;
|
|
62
62
|
}
|
|
63
|
+
/** The port `name` of the backend; 501 when the host serves none. The one place absent ports turn into an answer. */
|
|
64
|
+
function port(ctx, name) {
|
|
65
|
+
const found = ctx.ports[name];
|
|
66
|
+
if (found === void 0) throw new HttpError(501, `Port "${name}" not supported`);
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
63
69
|
/** Per-user limit for the AI handlers: 401 without a user, 429 with `Retry-After` when exceeded; returns the user, for `resolveModel`. */
|
|
64
70
|
async function enforceRateLimit(ctx, request) {
|
|
65
71
|
const user = await authenticated(ctx, request);
|
|
@@ -80,6 +86,6 @@ async function readBody(request, schema) {
|
|
|
80
86
|
return parsed.data;
|
|
81
87
|
}
|
|
82
88
|
//#endregion
|
|
83
|
-
export { authenticated, empty, enforceRateLimit, eventStream, json, readBody, text };
|
|
89
|
+
export { authenticated, empty, enforceRateLimit, eventStream, json, port, readBody, text };
|
|
84
90
|
|
|
85
91
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../src/server/handlers/context.ts"],"sourcesContent":["import type { AiModels } from '../ai'\nimport type {
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../../src/server/handlers/context.ts"],"sourcesContent":["import type { AiModels } from '../ai'\nimport type { EditorPorts } from '../ports'\nimport type { CollabServer } from '../collab'\nimport { HttpError } from '../errors'\nimport type { RateLimiter } from '../rate-limit'\nimport { userKey } from '../rate-limit'\n\n/** What every editor handler needs: the ports of the backend (identity first). */\nexport interface HandlerContext {\n ports: EditorPorts\n /** The collaboration server of the process: documents open in it count for `tagsGet` with their unsaved content. */\n collab?: CollabServer\n /** What the installed server really serves (`capabilities` answers with it); without it, what `ports` imply. */\n served?: { ai?: boolean, collaboration?: boolean }\n}\n\n/** Context of the AI handlers: the configured models and the per-user limit shared by all of them. */\nexport interface AiHandlerContext extends HandlerContext {\n ai: AiModels\n limiter: RateLimiter\n}\n\n/** A request, reduced to what the handlers read: any HTTP server maps its own request type onto it. */\nexport interface HandlerRequest {\n headers: Headers\n /** Route parameters (`id`, `name`). */\n params?: Readonly<Record<string, string | undefined>>\n /** Query string values (`q`). */\n query?: Readonly<Record<string, string | undefined>>\n /** The JSON body. Lazy, so a handler authenticates (401) before it reads or validates anything. Rejects when the body is no JSON. */\n readJson?: () => Promise<unknown>\n /** The multipart body; `null` when it is none. Lazy like `readJson`. */\n readForm?: () => Promise<FormData | null>\n /** Aborts when the client goes away: handlers stop upstream requests with it. */\n signal?: AbortSignal\n}\n\n/** A response, as plain data any HTTP server can send (`new Response(body, { status, headers })` maps it 1:1). */\nexport interface HandlerResponse {\n status: number\n headers: Record<string, string>\n body: string | Uint8Array | ReadableStream<Uint8Array> | null\n}\n\nexport const json = (value: unknown, status = 200): HandlerResponse => ({\n status,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(value),\n})\n\nexport const text = (value: string, contentType: string, headers: Record<string, string> = {}): HandlerResponse => ({\n status: 200,\n headers: { 'content-type': contentType, ...headers },\n body: value,\n})\n\nexport const empty = (status = 204, headers: Record<string, string> = {}): HandlerResponse => ({ status, headers, body: null })\n\n/**\n * Server-sent events. `run` writes events through `emit` and ends the stream by returning; `signal` aborts when the\n * client cancels the response or `request.signal` aborts. An `emit` after that is dropped.\n */\nexport function eventStream(request: HandlerRequest, run: (emit: (event: string, data: string) => void, signal: AbortSignal) => Promise<void>): HandlerResponse {\n const abort = new AbortController()\n if (request.signal?.aborted) abort.abort()\n else request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n const encoder = new TextEncoder()\n return {\n status: 200,\n headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache, no-transform', 'x-accel-buffering': 'no' },\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n const emit = (event: string, data: string) => {\n if (!abort.signal.aborted) controller.enqueue(encoder.encode(`event: ${event}\\ndata: ${data}\\n\\n`))\n }\n void run(emit, abort.signal)\n .catch((error) => { if (!abort.signal.aborted) console.error('[editor-server]', error) })\n .finally(() => { try { controller.close() } catch { /* the client cancelled */ } })\n },\n cancel() { abort.abort() },\n }),\n }\n}\n\n/** The user `ports.identity.authenticate` finds behind the request's headers; 401 without one. */\nexport async function authenticated({ ports }: HandlerContext, request: HandlerRequest): Promise<object> {\n const user = await ports.identity.authenticate({ headers: request.headers })\n if (!user) throw new HttpError(401, 'Nicht angemeldet')\n return user\n}\n\n/** The port `name` of the backend; 501 when the host serves none. The one place absent ports turn into an answer. */\nexport function port<K extends Exclude<keyof EditorPorts, 'identity' | 'documents'>>(ctx: HandlerContext, name: K): NonNullable<EditorPorts[K]> {\n const found = ctx.ports[name]\n if (found === undefined) throw new HttpError(501, `Port \"${name}\" not supported`)\n return found as NonNullable<EditorPorts[K]>\n}\n\n/** Per-user limit for the AI handlers: 401 without a user, 429 with `Retry-After` when exceeded; returns the user, for `resolveModel`. */\nexport async function enforceRateLimit(ctx: AiHandlerContext, request: HandlerRequest): Promise<object> {\n const user = await authenticated(ctx, request)\n const waitMs = ctx.limiter.hit(userKey(user))\n if (!waitMs) return user\n throw new HttpError(429, 'Zu viele KI-Anfragen, bitte kurz warten', { 'retry-after': String(Math.max(1, Math.ceil(waitMs / 1000))) })\n}\n\ninterface BodySchema<T> {\n safeParse(data: unknown): { success: true, data: T } | { success: false, error: { issues: unknown[] } }\n}\n\n/** The request's JSON body validated with `schema`; 400 for malformed JSON and for validation failures (`data`: the issues). */\nexport async function readBody<T>(request: HandlerRequest, schema: BodySchema<T>): Promise<T> {\n let raw: unknown\n try {\n raw = await request.readJson?.()\n }\n catch {\n throw new HttpError(400, 'Ungültiger JSON-Body')\n }\n const parsed = schema.safeParse(raw)\n if (!parsed.success) throw new HttpError(400, 'Validation Error', {}, parsed.error.issues)\n return parsed.data\n}\n"],"mappings":";;;AA4CA,MAAa,QAAQ,OAAgB,SAAS,SAA0B;CACtE;CACA,SAAS,EAAE,gBAAgB,mBAAmB;CAC9C,MAAM,KAAK,UAAU,KAAK;AAC5B;AAEA,MAAa,QAAQ,OAAe,aAAqB,UAAkC,CAAC,OAAwB;CAClH,QAAQ;CACR,SAAS;EAAE,gBAAgB;EAAa,GAAG;CAAQ;CACnD,MAAM;AACR;AAEA,MAAa,SAAS,SAAS,KAAK,UAAkC,CAAC,OAAwB;CAAE;CAAQ;CAAS,MAAM;AAAK;;;;;AAM7H,SAAgB,YAAY,SAAyB,KAA2G;CAC9J,MAAM,QAAQ,IAAI,gBAAgB;CAClC,IAAI,QAAQ,QAAQ,SAAS,MAAM,MAAM;MACpC,QAAQ,QAAQ,iBAAiB,eAAe,MAAM,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;CAClF,MAAM,UAAU,IAAI,YAAY;CAChC,OAAO;EACL,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAqB,iBAAiB;GAA0B,qBAAqB;EAAK;EACrH,MAAM,IAAI,eAA2B;GACnC,MAAM,YAAY;IAChB,MAAM,QAAQ,OAAe,SAAiB;KAC5C,IAAI,CAAC,MAAM,OAAO,SAAS,WAAW,QAAQ,QAAQ,OAAO,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC;IACpG;IACA,IAAS,MAAM,MAAM,MAAM,CAAC,CACzB,OAAO,UAAU;KAAE,IAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,MAAM,mBAAmB,KAAK;IAAE,CAAC,CAAC,CACxF,cAAc;KAAE,IAAI;MAAE,WAAW,MAAM;KAAE,QAAQ,CAA6B;IAAE,CAAC;GACtF;GACA,SAAS;IAAE,MAAM,MAAM;GAAE;EAC3B,CAAC;CACH;AACF;;AAGA,eAAsB,cAAc,EAAE,SAAyB,SAA0C;CACvG,MAAM,OAAO,MAAM,MAAM,SAAS,aAAa,EAAE,SAAS,QAAQ,QAAQ,CAAC;CAC3E,IAAI,CAAC,MAAM,MAAM,IAAI,UAAU,KAAK,kBAAkB;CACtD,OAAO;AACT;;AAGA,SAAgB,KAAqE,KAAqB,MAAsC;CAC9I,MAAM,QAAQ,IAAI,MAAM;CACxB,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,UAAU,KAAK,SAAS,KAAK,gBAAgB;CAChF,OAAO;AACT;;AAGA,eAAsB,iBAAiB,KAAuB,SAA0C;CACtG,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,MAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ,IAAI,CAAC;CAC5C,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,IAAI,UAAU,KAAK,2CAA2C,EAAE,eAAe,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,GAAI,CAAC,CAAC,EAAE,CAAC;AACtI;;AAOA,eAAsB,SAAY,SAAyB,QAAmC;CAC5F,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,QAAQ,WAAW;CACjC,QACM;EACJ,MAAM,IAAI,UAAU,KAAK,sBAAsB;CACjD;CACA,MAAM,SAAS,OAAO,UAAU,GAAG;CACnC,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,UAAU,KAAK,oBAAoB,CAAC,GAAG,OAAO,MAAM,MAAM;CACzF,OAAO,OAAO;AAChB"}
|