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