@ecsplico/editor 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +4 -3
- package/dist/adapters/anchors.d.ts +80 -0
- package/dist/adapters/anchors.js +270 -0
- package/dist/adapters/anchors.js.map +1 -0
- package/dist/adapters/comments/client.d.ts +64 -0
- package/dist/adapters/comments/client.js +536 -0
- package/dist/adapters/comments/client.js.map +1 -0
- package/dist/adapters/comments/drizzle.d.ts +430 -0
- package/dist/adapters/comments/drizzle.js +236 -0
- package/dist/adapters/comments/drizzle.js.map +1 -0
- package/dist/adapters/comments/errors.d.ts +9 -0
- package/dist/adapters/comments/errors.js +19 -0
- package/dist/adapters/comments/errors.js.map +1 -0
- package/dist/adapters/comments/json.d.ts +19 -0
- package/dist/adapters/comments/json.js +94 -0
- package/dist/adapters/comments/json.js.map +1 -0
- package/dist/adapters/comments/record.js +78 -0
- package/dist/adapters/comments/record.js.map +1 -0
- package/dist/adapters/comments/server.d.ts +23 -0
- package/dist/adapters/comments/server.js +102 -0
- package/dist/adapters/comments/server.js.map +1 -0
- package/dist/adapters/comments/types.d.ts +109 -0
- package/dist/adapters/documents.js +108 -0
- package/dist/adapters/documents.js.map +1 -0
- package/dist/adapters/drizzle.d.ts +293 -0
- package/dist/adapters/drizzle.js +157 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/files.js +30 -0
- package/dist/adapters/files.js.map +1 -0
- package/dist/adapters/forge.d.ts +58 -0
- package/dist/adapters/forge.js +91 -0
- package/dist/adapters/forge.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +0 -0
- package/dist/adapters/json.d.ts +24 -0
- package/dist/adapters/json.js +128 -0
- package/dist/adapters/json.js.map +1 -0
- package/dist/adapters/nitro.d.ts +61 -0
- package/dist/adapters/nitro.js +212 -0
- package/dist/adapters/nitro.js.map +1 -0
- package/dist/adapters/routes.js +16 -0
- package/dist/adapters/routes.js.map +1 -0
- package/dist/adapters/shared.d.ts +37 -0
- package/dist/adapters/shared.js +32 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/vue.d.ts +67 -0
- package/dist/adapters/vue.js +187 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/core/doc-anchors.d.ts +5 -0
- package/dist/core/doc-anchors.js +4 -3
- package/dist/core/doc-anchors.js.map +1 -1
- package/dist/core/editor-ui.js +57 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/surface-session.js +139 -0
- package/dist/core/surface-session.js.map +1 -0
- package/dist/document/DocumentSurface.d.ts +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/agent-op-review.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js.map +1 -1
- package/dist/features/ai/agent-ops.d.ts +1 -1
- package/dist/features/ai/agent-ops.js +9 -6
- package/dist/features/ai/agent-ops.js.map +1 -1
- package/dist/features/ai/ai-apply.d.ts +1 -1
- package/dist/features/ai/ai-review.js +10 -1
- package/dist/features/ai/ai-review.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +78 -252
- package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/DocumentChatOps.js +1 -0
- package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat.d.ts +3 -2
- package/dist/features/ai/document-chat.js +16 -19
- package/dist/features/ai/document-chat.js.map +1 -1
- package/dist/features/ai/index.d.ts +5 -3
- package/dist/features/ai/index.js +4 -2
- package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
- package/dist/features/ai/protocol/agent-ops.js +65 -39
- package/dist/features/ai/protocol/agent-ops.js.map +1 -1
- package/dist/features/ai/protocol/block-lines.d.ts +89 -0
- package/dist/features/ai/protocol/block-lines.js +177 -0
- package/dist/features/ai/protocol/block-lines.js.map +1 -0
- package/dist/features/ai/protocol/chat.js +10 -10
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +3 -2
- package/dist/features/ai/protocol/index.js +3 -2
- package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
- package/dist/features/ai/react/AiSelectionMenu.js +64 -255
- package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/react/DocumentChatOps.js +1 -0
- package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
- package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
- package/dist/features/ai/selection-flow.d.ts +133 -0
- package/dist/features/ai/selection-flow.js +283 -0
- package/dist/features/ai/selection-flow.js.map +1 -0
- package/dist/features/ai/use-ai-assistant.d.ts +1 -1
- package/dist/features/ai/use-document-chat.d.ts +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/feature.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
- package/dist/features/comments/components/CommentBubble.js +1 -1
- package/dist/features/comments/components/CommentComposer.js +1 -1
- package/dist/features/comments/components/CommentThreadView.js +5 -2
- package/dist/features/comments/components/CommentThreadView.js.map +1 -1
- package/dist/features/comments/components/CommentsMargin.js +108 -230
- package/dist/features/comments/components/CommentsMargin.js.map +1 -1
- package/dist/features/comments/components/CommentsOverlay.js +1 -1
- package/dist/features/comments/feature.js +1 -1
- package/dist/features/comments/index.d.ts +4 -2
- package/dist/features/comments/index.js +4 -2
- package/dist/features/comments/margin-controller.d.ts +44 -0
- package/dist/features/comments/margin-controller.js +132 -0
- package/dist/features/comments/margin-controller.js.map +1 -0
- package/dist/features/comments/margin-layout.d.ts +55 -0
- package/dist/features/comments/margin-layout.js +79 -0
- package/dist/features/comments/margin-layout.js.map +1 -0
- package/dist/features/comments/model.d.ts +8 -0
- package/dist/features/comments/model.js +60 -34
- package/dist/features/comments/model.js.map +1 -1
- package/dist/features/comments/react/CommentBubble.js +1 -1
- package/dist/features/comments/react/CommentComposer.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +2 -2
- package/dist/features/comments/react/CommentThreadView.js.map +1 -1
- package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
- package/dist/features/comments/react/CommentsMargin.js +85 -251
- package/dist/features/comments/react/CommentsMargin.js.map +1 -1
- package/dist/features/comments/react/CommentsOverlay.js +1 -1
- package/dist/features/comments/react/use-comments.js +1 -1
- package/dist/features/comments/react/use-margin-layout.js +45 -0
- package/dist/features/comments/react/use-margin-layout.js.map +1 -0
- package/dist/features/comments/react.d.ts +1 -1
- package/dist/features/comments/schema.d.ts +23 -0
- package/dist/features/comments/schema.js.map +1 -1
- package/dist/features/comments/snapshot-equality.js +40 -0
- package/dist/features/comments/snapshot-equality.js.map +1 -0
- package/dist/features/comments/thread-anchor.js +33 -0
- package/dist/features/comments/thread-anchor.js.map +1 -0
- package/dist/features/comments/use-comments.d.ts +3 -1
- package/dist/features/comments/use-comments.js +1 -0
- package/dist/features/comments/use-comments.js.map +1 -1
- package/dist/features/comments/use-margin-layout.js +29 -0
- package/dist/features/comments/use-margin-layout.js.map +1 -0
- package/dist/features/comments/vue.d.ts +1 -1
- package/dist/features/composites/accepted-children.d.ts +21 -0
- package/dist/features/composites/accepted-children.js +126 -0
- package/dist/features/composites/accepted-children.js.map +1 -0
- package/dist/features/composites/components/CompositeDetails.js +1 -1
- package/dist/features/composites/components/CompositeInspector.js +38 -164
- package/dist/features/composites/components/CompositeInspector.js.map +1 -1
- package/dist/features/composites/feature.d.ts +2 -1
- package/dist/features/composites/feature.js +8 -2
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/index.d.ts +5 -3
- package/dist/features/composites/index.js +8 -6
- package/dist/features/composites/inspector-model.d.ts +97 -0
- package/dist/features/composites/inspector-model.js +255 -0
- package/dist/features/composites/inspector-model.js.map +1 -0
- package/dist/features/composites/kinds.d.ts +11 -0
- package/dist/features/composites/kinds.js +17 -2
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/outline.d.ts +18 -5
- package/dist/features/composites/outline.js +44 -12
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
- package/dist/features/composites/react/CompositeInspector.js +21 -180
- package/dist/features/composites/react/CompositeInspector.js.map +1 -1
- package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
- package/dist/features/composites/react/CompositeView.js +1 -1
- package/dist/features/composites/react/use-composite-inspector.js +29 -0
- package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/schema.d.ts +2 -2
- package/dist/features/composites/schema.js +2 -2
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/use-composite-anchors.d.ts +1 -1
- package/dist/features/composites/use-composite-inspector.js +41 -0
- package/dist/features/composites/use-composite-inspector.js.map +1 -0
- package/dist/features/composites/use-composite-rows.d.ts +1 -1
- package/dist/features/media/index.d.ts +1 -1
- package/dist/features/media/vue.d.ts +1 -1
- package/dist/features/mentions/index.d.ts +3 -3
- package/dist/features/mentions/index.js +3 -3
- package/dist/features/mentions/schema.d.ts +1 -1
- package/dist/features/mentions/schema.js +1 -1
- package/dist/features/themes/index.d.ts +1 -1
- package/dist/features/themes/index.js +1 -1
- package/dist/features/transcript/feature.js +1 -1
- package/dist/features/transcript/index.js +2 -2
- package/dist/i18n/de/ai.js +2 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +8 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/tree.js +14 -1
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +22 -0
- package/dist/i18n/en/ai.js +2 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +8 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/tree.js +14 -1
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +2 -2
- package/dist/integrations/composite-ai/composite-summary.js +2 -2
- package/dist/integrations/composite-ai/index.js +2 -2
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.d.ts +1 -1
- package/dist/markdown/MarkdownSurface.js +2 -2
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/markdown/index.d.ts +2 -2
- package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
- package/dist/react/EditorSurface.d.ts +1 -1
- package/dist/react/EditorSurface.js +43 -149
- package/dist/react/EditorSurface.js.map +1 -1
- package/dist/react/EditorToolbar.js +3 -40
- package/dist/react/EditorToolbar.js.map +1 -1
- package/dist/schema/advanced.js +2 -2
- package/dist/server/adapter.d.ts +2 -2
- package/dist/server/collab.js +1 -1
- package/dist/server/handlers/ai.d.ts +4 -2
- package/dist/server/handlers/ai.js +11 -9
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +1 -1
- package/dist/server/handlers/editor.d.ts +1 -1
- package/dist/server/handlers/editor.js +2 -2
- package/dist/server/host.d.ts +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/styles/tokens.css +1 -1
- package/dist/tree/DocumentTree.js +159 -387
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +37 -0
- package/dist/tree/drop-rules.js.map +1 -0
- package/dist/tree/react/DocumentTree.d.ts +2 -2
- package/dist/tree/react/DocumentTree.js +135 -397
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/react/use-tree-model.js +129 -0
- package/dist/tree/react/use-tree-model.js.map +1 -0
- package/dist/tree/react.js +1 -1
- package/dist/tree/tree-host.js +100 -0
- package/dist/tree/tree-host.js.map +1 -0
- package/dist/tree/tree-items.js +22 -43
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +580 -0
- package/dist/tree/tree-model.js.map +1 -0
- package/dist/tree/tree-rows.js +77 -0
- package/dist/tree/tree-rows.js.map +1 -0
- package/dist/tree/use-tree-model.js +94 -0
- package/dist/tree/use-tree-model.js.map +1 -0
- package/dist/tree/vue.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +2 -2
- package/dist/vue/components/EditorSurface.js +27 -87
- package/dist/vue/components/EditorSurface.js.map +1 -1
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/components/EditorToolbar.js +7 -44
- package/dist/vue/components/EditorToolbar.js.map +1 -1
- package/dist/vue/context.d.ts +1 -1
- package/dist/vue/context.js +1 -1
- package/dist/vue/context.js.map +1 -1
- package/dist/vue/use-editor-ui.d.ts +1 -1
- package/dist/vue/use-notifier.d.ts +1 -1
- package/docs/integration.md +97 -4
- package/docs/llms.md +9 -1
- package/docs/usage.md +10 -5
- package/package.json +67 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentChatOps.js","names":[],"sources":["../../../../src/features/ai/react/DocumentChatOps.tsx"],"sourcesContent":["import { type ConversionProvider, conversionLabel, parseConversionTarget } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertDescription, AlertTitle } from '../../../react/ui/alert'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport type { AgentOpState } from '../agent-op-review'\nimport { AGENT_OP_OUTCOME_KEYS } from '../agent-ops'\nimport type { AgentOp, AgentOpOutcome, AgentOpProblem } from '../protocol'\n\nconst ICONS: Record<AgentOp['type'], string> = { add: 'i-lucide-plus', update: 'i-lucide-pencil', delete: 'i-lucide-trash-2', convert: 'i-lucide-replace' }\n\ntype BadgeTone = 'primary' | 'success' | 'neutral' | 'warning' | 'info'\n\nconst BADGE_TONES: Record<AgentOpOutcome, BadgeTone> = {\n pending: 'primary',\n accepted: 'success',\n rejected: 'neutral',\n partial: 'info',\n invalid: 'warning',\n unchanged: 'neutral',\n missing: 'warning',\n overlap: 'warning',\n empty: 'warning',\n blocked: 'warning',\n}\n\n/** The „subtle“ look of Nuxt UI's badge per color, on the vendored badge. */\nconst TONE_CLASS: Record<BadgeTone, string> = {\n primary: 'bg-primary/10 text-primary',\n success: 'bg-green-500/10 text-green-600 dark:text-green-400',\n neutral: 'bg-muted text-muted-foreground',\n warning: 'bg-amber-500/10 text-amber-600 dark:text-amber-400',\n info: 'bg-sky-500/10 text-sky-600 dark:text-sky-400',\n}\n\n/** The edit operations of one chat answer with their review state; open ones can be decided right here. */\nexport function DocumentChatOps({ ops, states, problems, providers, onDecide, onReveal }: {\n ops: AgentOp[]\n states: Readonly<Record<string, AgentOpState>>\n /** Operations the server dropped (invalid block IDs after the retries). */\n problems?: AgentOpProblem[]\n /** The editor's conversion providers, to name `convert` targets. */\n providers: readonly ConversionProvider[]\n onDecide: (id: string, accept: boolean) => void\n onReveal: (op: AgentOp) => void\n}) {\n const { t } = useEditorI18n()\n\n function label(op: AgentOp, snippet: string): string {\n if (op.type === 'add') return op.afterId === null ? t('ai.op.addAtStart') : t('ai.op.addAfter', { snippet })\n if (op.type === 'convert') {\n const target = parseConversionTarget(providers, op.to)\n return t('ai.op.convert', { target: target ? conversionLabel(providers, target, t).label : op.to, snippet })\n }\n return t(op.type === 'update' ? 'ai.op.update' : 'ai.op.delete', { snippet })\n }\n\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {ops.map((op) => {\n const state = states[op.op]\n return (\n <div key={op.op} className=\"flex items-center gap-2 rounded-md border border-border px-2 py-1\" data-agent-op={op.op}>\n <Icon name={ICONS[op.type]} className=\"text-muted-foreground\" />\n <button\n type=\"button\"\n className=\"min-w-0 flex-1 truncate text-left text-xs hover:underline\"\n title={'markdown' in op ? op.markdown : undefined}\n onClick={() => onReveal(op)}\n >\n {label(op, state?.snippet ?? '')}\n </button>\n {state && (\n <Badge variant=\"secondary\" className={cn('h-auto py-px', TONE_CLASS[BADGE_TONES[state.outcome]])} title={t(AGENT_OP_OUTCOME_KEYS[state.outcome].hover)}>\n {t(AGENT_OP_OUTCOME_KEYS[state.outcome].badge)}\n </Badge>\n )}\n {state?.outcome === 'pending' && (\n <>\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('ai.op.discard')} onClick={() => onDecide(op.op, false)}>\n <Icon name=\"i-lucide-x\" />\n </Button>\n <Button size=\"icon-xs\" variant=\"ghost\" className=\"text-green-600 dark:text-green-400\" aria-label={t('ai.op.accept')} onClick={() => onDecide(op.op, true)}>\n <Icon name=\"i-lucide-check\" />\n </Button>\n </>\n )}\n </div>\n )\n })}\n {problems?.length\n ? (\n <Alert className=\"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\">\n <Icon name=\"i-lucide-triangle-alert\" />\n <AlertTitle className=\"text-xs\">{problems.length === 1 ? t('ai.op.problems.one') : t('ai.op.problems.other', { n: problems.length })}</AlertTitle>\n <AlertDescription className=\"text-xs\">{t('ai.op.problems.description')}</AlertDescription>\n </Alert>\n )\n : null}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,QAAyC;CAAE,KAAK;CAAiB,QAAQ;CAAmB,QAAQ;CAAoB,SAAS;AAAmB;AAI1J,MAAM,cAAiD;CACrD,SAAS;CACT,UAAU;CACV,UAAU;CACV,SAAS;CACT,SAAS;CACT,WAAW;CACX,SAAS;CACT,SAAS;CACT,OAAO;CACP,SAAS;AACX;;AAGA,MAAM,aAAwC;CAC5C,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,MAAM;AACR;;AAGA,SAAgB,gBAAgB,EAAE,KAAK,QAAQ,UAAU,WAAW,UAAU,YAS3E;CACD,MAAM,EAAE,MAAM,cAAc;CAE5B,SAAS,MAAM,IAAa,SAAyB;EACnD,IAAI,GAAG,SAAS,OAAO,OAAO,GAAG,YAAY,OAAO,EAAE,kBAAkB,IAAI,EAAE,kBAAkB,EAAE,QAAQ,CAAC;EAC3G,IAAI,GAAG,SAAS,WAAW;GACzB,MAAM,SAAS,sBAAsB,WAAW,GAAG,EAAE;GACrD,OAAO,EAAE,iBAAiB;IAAE,QAAQ,SAAS,gBAAgB,WAAW,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG;IAAI;GAAQ,CAAC;EAC7G;EACA,OAAO,EAAE,GAAG,SAAS,WAAW,iBAAiB,gBAAgB,EAAE,QAAQ,CAAC;CAC9E;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACG,IAAI,KAAK,OAAO;GACf,MAAM,QAAQ,OAAO,GAAG;GACxB,OACE,qBAAC,OAAD;IAAiB,WAAU;IAAoE,iBAAe,GAAG;IAAjH,UAAA;KACE,oBAAC,MAAD;MAAM,MAAM,MAAM,GAAG;MAAO,WAAU;KAAyB,CAAA;KAC/D,oBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,OAAO,cAAc,KAAK,GAAG,WAAW,KAAA;MACxC,eAAe,SAAS,EAAE;MAEzB,UAAA,MAAM,IAAI,OAAO,WAAW,EAAE;KACzB,CAAA;KACP,SACC,oBAAC,OAAD;MAAO,SAAQ;MAAY,WAAW,GAAG,gBAAgB,WAAW,YAAY,MAAM,SAAS;MAAG,OAAO,EAAE,sBAAsB,MAAM,QAAQ,CAAC,KAAK;MAClJ,UAAA,EAAE,sBAAsB,MAAM,QAAQ,CAAC,KAAK;KACxC,CAAA;KAER,OAAO,YAAY,aAClB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,cAAY,EAAE,eAAe;MAAG,eAAe,SAAS,GAAG,IAAI,KAAK;MACzG,UAAA,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;KACnB,CAAA,GACR,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,WAAU;MAAqC,cAAY,EAAE,cAAc;MAAG,eAAe,SAAS,GAAG,IAAI,IAAI;MACtJ,UAAA,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;KACvB,CAAA,CACR,EAAA,CAAA;IAED;GAzBK,GAAA,GAAG,EAyBR;EAET,CAAC,GACA,UAAU,SAEL,qBAAC,OAAD;GAAO,WAAU;GAAjB,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,0BAA2B,CAAA;IACtC,oBAAC,YAAD;KAAY,WAAU;KAAW,UAAA,SAAS,WAAW,IAAI,EAAE,oBAAoB,IAAI,EAAE,wBAAwB,EAAE,GAAG,SAAS,OAAO,CAAC;IAAc,CAAA;IACjJ,oBAAC,kBAAD;KAAkB,WAAU;KAAW,UAAA,EAAE,4BAA4B;IAAoB,CAAA;GACpF;EAET,CAAA,IAAA,IACD;;AAET"}
|
|
1
|
+
{"version":3,"file":"DocumentChatOps.js","names":[],"sources":["../../../../src/features/ai/react/DocumentChatOps.tsx"],"sourcesContent":["import { type ConversionProvider, conversionLabel, parseConversionTarget } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertDescription, AlertTitle } from '../../../react/ui/alert'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { cn } from '../../../react/ui/utils'\nimport type { AgentOpState } from '../agent-op-review'\nimport { AGENT_OP_OUTCOME_KEYS } from '../agent-ops'\nimport type { AgentOp, AgentOpOutcome, AgentOpProblem } from '../protocol'\n\nconst ICONS: Record<AgentOp['type'], string> = { add: 'i-lucide-plus', update: 'i-lucide-pencil', delete: 'i-lucide-trash-2', convert: 'i-lucide-replace' }\n\ntype BadgeTone = 'primary' | 'success' | 'neutral' | 'warning' | 'info'\n\nconst BADGE_TONES: Record<AgentOpOutcome, BadgeTone> = {\n pending: 'primary',\n accepted: 'success',\n rejected: 'neutral',\n partial: 'info',\n invalid: 'warning',\n unchanged: 'neutral',\n missing: 'warning',\n stale: 'warning',\n overlap: 'warning',\n empty: 'warning',\n blocked: 'warning',\n}\n\n/** The „subtle“ look of Nuxt UI's badge per color, on the vendored badge. */\nconst TONE_CLASS: Record<BadgeTone, string> = {\n primary: 'bg-primary/10 text-primary',\n success: 'bg-green-500/10 text-green-600 dark:text-green-400',\n neutral: 'bg-muted text-muted-foreground',\n warning: 'bg-amber-500/10 text-amber-600 dark:text-amber-400',\n info: 'bg-sky-500/10 text-sky-600 dark:text-sky-400',\n}\n\n/** The edit operations of one chat answer with their review state; open ones can be decided right here. */\nexport function DocumentChatOps({ ops, states, problems, providers, onDecide, onReveal }: {\n ops: AgentOp[]\n states: Readonly<Record<string, AgentOpState>>\n /** Operations the server dropped (invalid block IDs after the retries). */\n problems?: AgentOpProblem[]\n /** The editor's conversion providers, to name `convert` targets. */\n providers: readonly ConversionProvider[]\n onDecide: (id: string, accept: boolean) => void\n onReveal: (op: AgentOp) => void\n}) {\n const { t } = useEditorI18n()\n\n function label(op: AgentOp, snippet: string): string {\n if (op.type === 'add') return op.afterId === null ? t('ai.op.addAtStart') : t('ai.op.addAfter', { snippet })\n if (op.type === 'convert') {\n const target = parseConversionTarget(providers, op.to)\n return t('ai.op.convert', { target: target ? conversionLabel(providers, target, t).label : op.to, snippet })\n }\n return t(op.type === 'update' ? 'ai.op.update' : 'ai.op.delete', { snippet })\n }\n\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {ops.map((op) => {\n const state = states[op.op]\n return (\n <div key={op.op} className=\"flex items-center gap-2 rounded-md border border-border px-2 py-1\" data-agent-op={op.op}>\n <Icon name={ICONS[op.type]} className=\"text-muted-foreground\" />\n <button\n type=\"button\"\n className=\"min-w-0 flex-1 truncate text-left text-xs hover:underline\"\n title={'markdown' in op ? op.markdown : undefined}\n onClick={() => onReveal(op)}\n >\n {label(op, state?.snippet ?? '')}\n </button>\n {state && (\n <Badge variant=\"secondary\" className={cn('h-auto py-px', TONE_CLASS[BADGE_TONES[state.outcome]])} title={t(AGENT_OP_OUTCOME_KEYS[state.outcome].hover)}>\n {t(AGENT_OP_OUTCOME_KEYS[state.outcome].badge)}\n </Badge>\n )}\n {state?.outcome === 'pending' && (\n <>\n <Button size=\"icon-xs\" variant=\"ghost\" aria-label={t('ai.op.discard')} onClick={() => onDecide(op.op, false)}>\n <Icon name=\"i-lucide-x\" />\n </Button>\n <Button size=\"icon-xs\" variant=\"ghost\" className=\"text-green-600 dark:text-green-400\" aria-label={t('ai.op.accept')} onClick={() => onDecide(op.op, true)}>\n <Icon name=\"i-lucide-check\" />\n </Button>\n </>\n )}\n </div>\n )\n })}\n {problems?.length\n ? (\n <Alert className=\"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\">\n <Icon name=\"i-lucide-triangle-alert\" />\n <AlertTitle className=\"text-xs\">{problems.length === 1 ? t('ai.op.problems.one') : t('ai.op.problems.other', { n: problems.length })}</AlertTitle>\n <AlertDescription className=\"text-xs\">{t('ai.op.problems.description')}</AlertDescription>\n </Alert>\n )\n : null}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,QAAyC;CAAE,KAAK;CAAiB,QAAQ;CAAmB,QAAQ;CAAoB,SAAS;AAAmB;AAI1J,MAAM,cAAiD;CACrD,SAAS;CACT,UAAU;CACV,UAAU;CACV,SAAS;CACT,SAAS;CACT,WAAW;CACX,SAAS;CACT,OAAO;CACP,SAAS;CACT,OAAO;CACP,SAAS;AACX;;AAGA,MAAM,aAAwC;CAC5C,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,MAAM;AACR;;AAGA,SAAgB,gBAAgB,EAAE,KAAK,QAAQ,UAAU,WAAW,UAAU,YAS3E;CACD,MAAM,EAAE,MAAM,cAAc;CAE5B,SAAS,MAAM,IAAa,SAAyB;EACnD,IAAI,GAAG,SAAS,OAAO,OAAO,GAAG,YAAY,OAAO,EAAE,kBAAkB,IAAI,EAAE,kBAAkB,EAAE,QAAQ,CAAC;EAC3G,IAAI,GAAG,SAAS,WAAW;GACzB,MAAM,SAAS,sBAAsB,WAAW,GAAG,EAAE;GACrD,OAAO,EAAE,iBAAiB;IAAE,QAAQ,SAAS,gBAAgB,WAAW,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG;IAAI;GAAQ,CAAC;EAC7G;EACA,OAAO,EAAE,GAAG,SAAS,WAAW,iBAAiB,gBAAgB,EAAE,QAAQ,CAAC;CAC9E;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACG,IAAI,KAAK,OAAO;GACf,MAAM,QAAQ,OAAO,GAAG;GACxB,OACE,qBAAC,OAAD;IAAiB,WAAU;IAAoE,iBAAe,GAAG;IAAjH,UAAA;KACE,oBAAC,MAAD;MAAM,MAAM,MAAM,GAAG;MAAO,WAAU;KAAyB,CAAA;KAC/D,oBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,OAAO,cAAc,KAAK,GAAG,WAAW,KAAA;MACxC,eAAe,SAAS,EAAE;MAEzB,UAAA,MAAM,IAAI,OAAO,WAAW,EAAE;KACzB,CAAA;KACP,SACC,oBAAC,OAAD;MAAO,SAAQ;MAAY,WAAW,GAAG,gBAAgB,WAAW,YAAY,MAAM,SAAS;MAAG,OAAO,EAAE,sBAAsB,MAAM,QAAQ,CAAC,KAAK;MAClJ,UAAA,EAAE,sBAAsB,MAAM,QAAQ,CAAC,KAAK;KACxC,CAAA;KAER,OAAO,YAAY,aAClB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,cAAY,EAAE,eAAe;MAAG,eAAe,SAAS,GAAG,IAAI,KAAK;MACzG,UAAA,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;KACnB,CAAA,GACR,oBAAC,QAAD;MAAQ,MAAK;MAAU,SAAQ;MAAQ,WAAU;MAAqC,cAAY,EAAE,cAAc;MAAG,eAAe,SAAS,GAAG,IAAI,IAAI;MACtJ,UAAA,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;KACvB,CAAA,CACR,EAAA,CAAA;IAED;GAzBK,GAAA,GAAG,EAyBR;EAET,CAAC,GACA,UAAU,SAEL,qBAAC,OAAD;GAAO,WAAU;GAAjB,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,0BAA2B,CAAA;IACtC,oBAAC,YAAD;KAAY,WAAU;KAAW,UAAA,SAAS,WAAW,IAAI,EAAE,oBAAoB,IAAI,EAAE,wBAAwB,EAAE,GAAG,SAAS,OAAO,CAAC;IAAc,CAAA;IACjJ,oBAAC,kBAAD;KAAkB,WAAU;KAAW,UAAA,EAAE,4BAA4B;IAAoB,CAAA;GACpF;EAET,CAAA,IAAA,IACD;;AAET"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { MODEL_SLOT_KEYS } from "../protocol/models.js";
|
|
1
2
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
2
3
|
import { cn } from "../../../react/ui/utils.js";
|
|
3
4
|
import { Icon } from "../../../react/Icon.js";
|
|
4
5
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
5
6
|
import { useEditorUi } from "../../../react/editor-ui.js";
|
|
6
|
-
import { MODEL_SLOT_KEYS } from "../protocol/models.js";
|
|
7
7
|
import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
8
8
|
import { useModelProfiles } from "./use-model-profiles.js";
|
|
9
9
|
import { useState } from "react";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AI_LANGUAGES, MODEL_SLOTS } from "../../../settings/values.js";
|
|
2
|
+
import { AI_LANGUAGE_KEYS, MODEL_SLOT_KEYS } from "../protocol/models.js";
|
|
2
3
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
3
4
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
4
|
-
import { AI_LANGUAGE_KEYS, MODEL_SLOT_KEYS } from "../protocol/models.js";
|
|
5
5
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
6
6
|
import { ModelQuickSwitch } from "./ModelQuickSwitch.js";
|
|
7
7
|
import { SettingsSection } from "../../../react/SettingsSection.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fetchErrorMessage } from "../../../core/fetch-error.js";
|
|
2
|
+
import { modelRefSchema } from "../protocol/models.js";
|
|
2
3
|
import { useEditorContext } from "../../../react/context.js";
|
|
3
4
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
4
5
|
import { Icon } from "../../../react/Icon.js";
|
|
5
6
|
import { Button } from "../../../react/ui/button.js";
|
|
6
|
-
import { modelRefSchema } from "../protocol/models.js";
|
|
7
7
|
import { isRateLimited } from "../rate-limit.js";
|
|
8
8
|
import { Alert, AlertTitle } from "../../../react/ui/alert.js";
|
|
9
9
|
import { Input } from "../../../react/ui/input.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createAiSelectionFlow } from "../selection-flow.js";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
//#region src/features/ai/react/use-ai-selection-flow.ts
|
|
4
|
+
/**
|
|
5
|
+
* The AI selection menu's flow for `editor` (see `createAiSelectionFlow`), null until the first effect ran. The flow is
|
|
6
|
+
* created and disposed inside one effect, so React StrictMode's mount, cleanup, mount leaves exactly one live flow with
|
|
7
|
+
* one editor listener; it follows `editor`, `assistant`, `notify` and `t`. `language` is read each time the menu opens.
|
|
8
|
+
*/
|
|
9
|
+
function useAiSelectionFlow({ editor, assistant, notify, t, language }) {
|
|
10
|
+
const [flow, setFlow] = useState(null);
|
|
11
|
+
const latestLanguage = useRef(language);
|
|
12
|
+
latestLanguage.current = language;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const next = createAiSelectionFlow({
|
|
15
|
+
assistant,
|
|
16
|
+
notify,
|
|
17
|
+
t,
|
|
18
|
+
editor: () => editor,
|
|
19
|
+
language: () => latestLanguage.current()
|
|
20
|
+
});
|
|
21
|
+
setFlow(next);
|
|
22
|
+
return () => next.dispose();
|
|
23
|
+
}, [
|
|
24
|
+
editor,
|
|
25
|
+
assistant,
|
|
26
|
+
notify,
|
|
27
|
+
t
|
|
28
|
+
]);
|
|
29
|
+
return flow;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { useAiSelectionFlow };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=use-ai-selection-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-ai-selection-flow.js","names":[],"sources":["../../../../src/features/ai/react/use-ai-selection-flow.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEffect, useRef, useState } from 'react'\nimport type { Notifier } from '../../../core/notify'\nimport type { Translate } from '../../../i18n'\nimport type { AiLanguage } from '../../../settings'\nimport type { AiAssistant } from '../assistant'\nimport { type AiSelectionFlow, createAiSelectionFlow } from '../selection-flow'\n\n/**\n * The AI selection menu's flow for `editor` (see `createAiSelectionFlow`), null until the first effect ran. The flow is\n * created and disposed inside one effect, so React StrictMode's mount, cleanup, mount leaves exactly one live flow with\n * one editor listener; it follows `editor`, `assistant`, `notify` and `t`. `language` is read each time the menu opens.\n */\nexport function useAiSelectionFlow({ editor, assistant, notify, t, language }: { editor: Editor, assistant: AiAssistant, notify: Notifier, t: Translate, language: () => AiLanguage }): AiSelectionFlow | null {\n const [flow, setFlow] = useState<AiSelectionFlow | null>(null)\n const latestLanguage = useRef(language)\n latestLanguage.current = language\n\n useEffect(() => {\n const next = createAiSelectionFlow({ assistant, notify, t, editor: () => editor, language: () => latestLanguage.current() })\n setFlow(next)\n return () => next.dispose()\n }, [editor, assistant, notify, t])\n\n return flow\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,mBAAmB,EAAE,QAAQ,WAAW,QAAQ,GAAG,YAA4I;CAC7M,MAAM,CAAC,MAAM,WAAW,SAAiC,IAAI;CAC7D,MAAM,iBAAiB,OAAO,QAAQ;CACtC,eAAe,UAAU;CAEzB,gBAAgB;EACd,MAAM,OAAO,sBAAsB;GAAE;GAAW;GAAQ;GAAG,cAAc;GAAQ,gBAAgB,eAAe,QAAQ;EAAE,CAAC;EAC3H,QAAQ,IAAI;EACZ,aAAa,KAAK,QAAQ;CAC5B,GAAG;EAAC;EAAQ;EAAW;EAAQ;CAAC,CAAC;CAEjC,OAAO;AACT"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Translate } from "../../i18n/index.js";
|
|
2
|
+
import { AiLanguage } from "../../settings/values.js";
|
|
3
|
+
import "../../settings/index.js";
|
|
4
|
+
import { Notifier } from "../../core/notify.js";
|
|
5
|
+
import { ReadonlyStore } from "../../core/store.js";
|
|
6
|
+
import { AiTask } from "./protocol/tasks.js";
|
|
7
|
+
import "./protocol/index.js";
|
|
8
|
+
import { AiResultAction } from "./ai-apply.js";
|
|
9
|
+
import { AiMenuAction } from "./ai-menu.js";
|
|
10
|
+
import { AiAssistant, AiStatus } from "./assistant.js";
|
|
11
|
+
import { Editor } from "@tiptap/core";
|
|
12
|
+
//#region src/features/ai/selection-flow.d.ts
|
|
13
|
+
/** Plugin key of the bubble menu that offers the AI menu; the flow hides that bubble when it opens on the selection. */
|
|
14
|
+
export declare const AI_SELECTION_MENU_KEY = "aiSelectionMenu";
|
|
15
|
+
/** 'selection': rewrites the captured selection; 'caret': writes new text at the captured (collapsed) caret. */
|
|
16
|
+
export type AiSelectionMode = 'selection' | 'caret';
|
|
17
|
+
/** What the menu was asked to run; „Erneut versuchen“ repeats it. `language` only for tasks that `needsLanguage`. */
|
|
18
|
+
export interface AiSelectionRequest {
|
|
19
|
+
task: AiTask;
|
|
20
|
+
prompt: string;
|
|
21
|
+
language?: AiLanguage | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Everything a surface renders of the menu: its own state plus the assistant's streaming state. */
|
|
24
|
+
export interface AiSelectionState {
|
|
25
|
+
/** The menu is open on a captured range (selection) or caret. */
|
|
26
|
+
open: boolean;
|
|
27
|
+
mode: AiSelectionMode;
|
|
28
|
+
/** Caret mode: whether the document has text before the caret line, for tasks that continue it (`needsSelection`). */
|
|
29
|
+
hasTextBefore: boolean;
|
|
30
|
+
/** The quick actions of the registry for `mode`, in registry order. */
|
|
31
|
+
actions: readonly AiTask[];
|
|
32
|
+
/** The task the free prompt input starts (`custom` for a selection, `generate` at the caret). */
|
|
33
|
+
freePrompt: AiTask;
|
|
34
|
+
/** The free prompt input's text; cleared when the menu closes. */
|
|
35
|
+
prompt: string;
|
|
36
|
+
/** Target language of „Übersetzen“: the default each time the menu opens, changeable per call. */
|
|
37
|
+
language: AiLanguage;
|
|
38
|
+
request: AiSelectionRequest | null;
|
|
39
|
+
/** Id of the contributed menu action (`AiMenuAction`) that runs right now. */
|
|
40
|
+
runningAction: string | null;
|
|
41
|
+
status: AiStatus;
|
|
42
|
+
/** The result streamed so far. */
|
|
43
|
+
output: string;
|
|
44
|
+
error: string | null;
|
|
45
|
+
/** The task list is shown: nothing was requested yet, or the request ended without text (rate limit). */
|
|
46
|
+
picking: boolean;
|
|
47
|
+
/** The result is finished and non-blank: it may be applied. */
|
|
48
|
+
canApply: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface AiSelectionFlowOptions {
|
|
51
|
+
assistant: AiAssistant;
|
|
52
|
+
notify: Notifier;
|
|
53
|
+
/** The editor's translator (`FeatureContext.t`) for the notices the flow raises. */
|
|
54
|
+
t: Translate;
|
|
55
|
+
/**
|
|
56
|
+
* The current editor, read when a step runs, so a flow that continues after an await sees the latest one. The flow
|
|
57
|
+
* listens to the transactions of the editor present when it is created: create a new flow for another instance.
|
|
58
|
+
*/
|
|
59
|
+
editor: () => Editor | undefined;
|
|
60
|
+
/** The translation target language to start from, read each time the menu opens (the settings' default). */
|
|
61
|
+
language: () => AiLanguage;
|
|
62
|
+
}
|
|
63
|
+
export interface AiSelectionFlow {
|
|
64
|
+
state: ReadonlyStore<AiSelectionState>;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the bubble entry that opens the menu may show now: the menu is closed, the editor editable, the selection
|
|
67
|
+
* holds text and rewriting is allowed there. The surface adds its own focus condition (DOM) on top.
|
|
68
|
+
*/
|
|
69
|
+
offersSelection: () => boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Opens the menu on the selection: captures it (it survives edits made while the menu is open), highlights it and hides
|
|
72
|
+
* the bubble entry. Nothing happens while the menu is open, the editor is read-only or the selection is empty.
|
|
73
|
+
*/
|
|
74
|
+
openForSelection: () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Opens the menu at the caret in caret mode. Nothing happens while the menu is open, the editor is read-only or
|
|
77
|
+
* generating is not allowed at the caret.
|
|
78
|
+
*/
|
|
79
|
+
openAtCaret: () => void;
|
|
80
|
+
/** The captured range at its current positions (a caret: collapsed); null while closed or when the text is gone. */
|
|
81
|
+
range: () => {
|
|
82
|
+
from: number;
|
|
83
|
+
to: number;
|
|
84
|
+
} | null;
|
|
85
|
+
/** The extra rows other features contribute (`aiMenuActions`) that apply to the captured range right now. */
|
|
86
|
+
menuActions: () => AiMenuAction[];
|
|
87
|
+
/**
|
|
88
|
+
* Runs a contributed action on the captured range; the menu closes afterwards, unless it was closed or reopened
|
|
89
|
+
* meanwhile. One action at a time. Closing the menu aborts the action's `signal`. A rejection of the action
|
|
90
|
+
* propagates and leaves the menu open.
|
|
91
|
+
*/
|
|
92
|
+
runAction: (action: AiMenuAction) => Promise<void>;
|
|
93
|
+
setPrompt: (text: string) => void;
|
|
94
|
+
setLanguage: (language: AiLanguage) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Runs `task` (with `prompt` for a free instruction) on the current text of the captured range and the current
|
|
97
|
+
* document, streaming the result into `state.output`. The returned promise settles when the run ended; a stopped,
|
|
98
|
+
* failed or rate-limited run leaves its outcome in `state` (a 429 also raises the assistant's notice). Without an
|
|
99
|
+
* open menu nothing happens.
|
|
100
|
+
*/
|
|
101
|
+
start: (task: AiTask, prompt?: string) => Promise<void>;
|
|
102
|
+
/** Starts the free prompt of the mode with `state.prompt`; nothing happens while it is blank. */
|
|
103
|
+
submitPrompt: () => Promise<void>;
|
|
104
|
+
/** „Erneut versuchen“: runs the last request again on the range's and document's current text. */
|
|
105
|
+
retry: () => Promise<void>;
|
|
106
|
+
/** Stops the streaming run; the partial result stays and can only be discarded. */
|
|
107
|
+
abort: () => void;
|
|
108
|
+
/**
|
|
109
|
+
* „Ersetzen“ opens the in-document review of the result over the captured range (nothing is written until it is
|
|
110
|
+
* accepted there), „Darunter einfügen“ inserts it below. Closes the menu on success (and when the result equals the
|
|
111
|
+
* text); otherwise notifies why not and stays open. Only a finished, non-blank result is applied.
|
|
112
|
+
*/
|
|
113
|
+
apply: (action: AiResultAction) => void;
|
|
114
|
+
/** Caret mode „Einfügen“: inserts the result at the captured caret line and closes the menu; notifies when the caret is gone. */
|
|
115
|
+
insertAtCaret: () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Closes the menu: stops the run and a contributed action, drops request and prompt, removes the highlight and
|
|
118
|
+
* releases the captured range. `restoreSelection` selects that range (the caret in caret mode) again, so the
|
|
119
|
+
* editor is where it was before. Does nothing while closed.
|
|
120
|
+
*/
|
|
121
|
+
close: (restoreSelection: boolean) => void;
|
|
122
|
+
/** Closes the menu and stops listening to the editor and the assistant. Idempotent; the flow does not open again. */
|
|
123
|
+
dispose: () => void;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The flow of the AI quick-action menu on a selection or at the caret, shared by both UI surfaces: they render
|
|
127
|
+
* `state` (and the preview of `state.output`), place the popover and call this. The captured range and the result's
|
|
128
|
+
* apply target are DocAnchors, so a result still lands on the captured text after the document changed elsewhere.
|
|
129
|
+
* Listens to the editor's transactions for `openAiMenuForSelection` / `openAiMenuAtCaret` requests.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createAiSelectionFlow({ assistant, notify, t, editor: getEditor, language: defaultLanguage }: AiSelectionFlowOptions): AiSelectionFlow;
|
|
132
|
+
//#endregion
|
|
133
|
+
//# sourceMappingURL=selection-flow.d.ts.map
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { ABILITY } from "../../core/schema/abilities.js";
|
|
2
|
+
import { abilitiesAtSelection, hasAbility } from "../../core/abilities.js";
|
|
3
|
+
import { aiContextOf } from "../../core/pipeline.js";
|
|
4
|
+
import { createStore } from "../../core/store.js";
|
|
5
|
+
import { captureRange, releaseRanges, resolveRange } from "../../core/doc-anchors.js";
|
|
6
|
+
import { selectionRange } from "../../core/selection-range.js";
|
|
7
|
+
import { AI_LIMITS, AI_TASKS } from "./protocol/tasks.js";
|
|
8
|
+
import { aiApplyFailure, applyAiResult, documentMarkdown, insertAtCaretLine, rangeMarkdown } from "./ai-apply.js";
|
|
9
|
+
import { aiMenuActionsOf } from "./ai-menu.js";
|
|
10
|
+
import { hideAiSelectionHighlight, showAiSelectionHighlight } from "./ai-selection-highlight.js";
|
|
11
|
+
import { TextSelection } from "@tiptap/pm/state";
|
|
12
|
+
//#region src/features/ai/selection-flow.ts
|
|
13
|
+
/** Plugin key of the bubble menu that offers the AI menu; the flow hides that bubble when it opens on the selection. */
|
|
14
|
+
const AI_SELECTION_MENU_KEY = "aiSelectionMenu";
|
|
15
|
+
const SELECTION_ACTIONS = Object.values(AI_TASKS).filter((task) => task.selectionMenu && !task.needsPrompt);
|
|
16
|
+
const CARET_ACTIONS = Object.values(AI_TASKS).filter((task) => task.caretMenu);
|
|
17
|
+
/**
|
|
18
|
+
* The flow of the AI quick-action menu on a selection or at the caret, shared by both UI surfaces: they render
|
|
19
|
+
* `state` (and the preview of `state.output`), place the popover and call this. The captured range and the result's
|
|
20
|
+
* apply target are DocAnchors, so a result still lands on the captured text after the document changed elsewhere.
|
|
21
|
+
* Listens to the editor's transactions for `openAiMenuForSelection` / `openAiMenuAtCaret` requests.
|
|
22
|
+
*/
|
|
23
|
+
function createAiSelectionFlow({ assistant, notify, t, editor: getEditor, language: defaultLanguage }) {
|
|
24
|
+
/** The captured range; null while closed. Compared by identity to tell whether the menu was reopened meanwhile. */
|
|
25
|
+
let anchor = null;
|
|
26
|
+
let actionController = null;
|
|
27
|
+
let disposed = false;
|
|
28
|
+
let own = {
|
|
29
|
+
open: false,
|
|
30
|
+
mode: "selection",
|
|
31
|
+
hasTextBefore: false,
|
|
32
|
+
prompt: "",
|
|
33
|
+
language: defaultLanguage(),
|
|
34
|
+
request: null,
|
|
35
|
+
runningAction: null
|
|
36
|
+
};
|
|
37
|
+
function compose() {
|
|
38
|
+
const { status, output, error } = assistant.state.get();
|
|
39
|
+
const caret = own.mode === "caret";
|
|
40
|
+
return {
|
|
41
|
+
...own,
|
|
42
|
+
actions: caret ? CARET_ACTIONS : SELECTION_ACTIONS,
|
|
43
|
+
freePrompt: caret ? AI_TASKS.generate : AI_TASKS.custom,
|
|
44
|
+
status,
|
|
45
|
+
output,
|
|
46
|
+
error,
|
|
47
|
+
picking: status === "idle" && !output,
|
|
48
|
+
canApply: status === "done" && !!output.trim()
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const store = createStore(compose());
|
|
52
|
+
const stopAssistant = assistant.state.subscribe(() => store.set(compose()));
|
|
53
|
+
function update(patch) {
|
|
54
|
+
own = {
|
|
55
|
+
...own,
|
|
56
|
+
...patch
|
|
57
|
+
};
|
|
58
|
+
store.set(compose());
|
|
59
|
+
}
|
|
60
|
+
const range = () => {
|
|
61
|
+
const editor = getEditor();
|
|
62
|
+
return editor && !editor.isDestroyed && anchor ? resolveRange(editor.state, anchor) : null;
|
|
63
|
+
};
|
|
64
|
+
function offersSelection() {
|
|
65
|
+
const editor = getEditor();
|
|
66
|
+
if (!editor || own.open || !editor.isEditable) return false;
|
|
67
|
+
const { selection, doc } = editor.state;
|
|
68
|
+
if (selection.empty || doc.textBetween(selection.from, selection.to, " ").trim().length === 0) return false;
|
|
69
|
+
return abilitiesAtSelection(editor).has(ABILITY.aiRewrite);
|
|
70
|
+
}
|
|
71
|
+
function openForSelection() {
|
|
72
|
+
const editor = getEditor();
|
|
73
|
+
if (disposed || !editor || own.open || !editor.isEditable) return;
|
|
74
|
+
const selected = selectionRange(editor.state);
|
|
75
|
+
if (selected.from >= selected.to) return;
|
|
76
|
+
const captured = captureRange(editor, selected);
|
|
77
|
+
anchor = captured;
|
|
78
|
+
update({
|
|
79
|
+
open: true,
|
|
80
|
+
mode: "selection",
|
|
81
|
+
hasTextBefore: false,
|
|
82
|
+
prompt: "",
|
|
83
|
+
language: defaultLanguage(),
|
|
84
|
+
request: null
|
|
85
|
+
});
|
|
86
|
+
showAiSelectionHighlight(editor, captured);
|
|
87
|
+
editor.view.dispatch(editor.state.tr.setMeta(AI_SELECTION_MENU_KEY, "hide"));
|
|
88
|
+
}
|
|
89
|
+
function openAtCaret() {
|
|
90
|
+
const editor = getEditor();
|
|
91
|
+
if (disposed || !editor || own.open || !editor.isEditable || !hasAbility(editor, ABILITY.aiGenerate)) return;
|
|
92
|
+
const { $head, head } = editor.state.selection;
|
|
93
|
+
const hasTextBefore = editor.state.doc.textBetween(0, $head.depth ? $head.before() : head, " ", " ").trim().length > 0;
|
|
94
|
+
anchor = captureRange(editor, {
|
|
95
|
+
from: head,
|
|
96
|
+
to: head
|
|
97
|
+
});
|
|
98
|
+
update({
|
|
99
|
+
open: true,
|
|
100
|
+
mode: "caret",
|
|
101
|
+
hasTextBefore,
|
|
102
|
+
prompt: "",
|
|
103
|
+
language: defaultLanguage(),
|
|
104
|
+
request: null
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function onTransaction({ transaction }) {
|
|
108
|
+
if (transaction.getMeta("aiMenuAtCaret")) openAtCaret();
|
|
109
|
+
if (transaction.getMeta("aiMenuForSelection")) openForSelection();
|
|
110
|
+
}
|
|
111
|
+
const listened = getEditor();
|
|
112
|
+
listened?.on("transaction", onTransaction);
|
|
113
|
+
function menuActions() {
|
|
114
|
+
const editor = getEditor();
|
|
115
|
+
const resolved = range();
|
|
116
|
+
return editor && resolved ? aiMenuActionsOf(editor).filter((action) => action.available?.(editor, resolved) ?? true) : [];
|
|
117
|
+
}
|
|
118
|
+
async function runAction(action) {
|
|
119
|
+
const editor = getEditor();
|
|
120
|
+
const captured = anchor;
|
|
121
|
+
const resolved = range();
|
|
122
|
+
if (!editor || !captured || !resolved || actionController) return;
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
actionController = controller;
|
|
125
|
+
update({ runningAction: action.id });
|
|
126
|
+
try {
|
|
127
|
+
await action.run(editor, resolved, controller.signal);
|
|
128
|
+
} finally {
|
|
129
|
+
if (actionController === controller) {
|
|
130
|
+
actionController = null;
|
|
131
|
+
update({ runningAction: null });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (anchor === captured) close(false);
|
|
135
|
+
}
|
|
136
|
+
/** Runs `request` on the current text of the captured range, with the current document as context. */
|
|
137
|
+
async function generate(request) {
|
|
138
|
+
const editor = getEditor();
|
|
139
|
+
const captured = anchor;
|
|
140
|
+
if (!editor || !captured) return;
|
|
141
|
+
const at = resolveRange(editor.state, captured);
|
|
142
|
+
if (own.mode === "caret") {
|
|
143
|
+
if (!at) {
|
|
144
|
+
notify({
|
|
145
|
+
title: t("ai.apply.missing.title"),
|
|
146
|
+
description: t("ai.menu.missing.line"),
|
|
147
|
+
kind: "error"
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const { doc } = editor.state;
|
|
152
|
+
const $at = doc.resolve(at.from);
|
|
153
|
+
const splitBefore = $at.depth ? $at.before() : at.from;
|
|
154
|
+
const splitAfter = $at.depth ? $at.after() : at.from;
|
|
155
|
+
const continues = request.task.needsSelection;
|
|
156
|
+
const { blocks } = aiContextOf(editor);
|
|
157
|
+
await assistant.run({
|
|
158
|
+
task: request.task.id,
|
|
159
|
+
prompt: request.prompt,
|
|
160
|
+
selection: continues ? rangeMarkdown(editor, {
|
|
161
|
+
from: 0,
|
|
162
|
+
to: splitBefore
|
|
163
|
+
}).slice(-AI_LIMITS.selection) : "",
|
|
164
|
+
context: continues ? rangeMarkdown(editor, {
|
|
165
|
+
from: splitAfter,
|
|
166
|
+
to: doc.content.size
|
|
167
|
+
}).slice(0, AI_LIMITS.context) : documentMarkdown(editor),
|
|
168
|
+
blocks
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!at || at.from >= at.to) {
|
|
173
|
+
notify({
|
|
174
|
+
title: t("ai.apply.missing.title"),
|
|
175
|
+
description: t("ai.menu.missing.selection"),
|
|
176
|
+
kind: "error"
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const { blocks } = aiContextOf(editor);
|
|
181
|
+
await assistant.run({
|
|
182
|
+
task: request.task.id,
|
|
183
|
+
prompt: request.prompt,
|
|
184
|
+
...request.language && { language: request.language },
|
|
185
|
+
selection: rangeMarkdown(editor, at),
|
|
186
|
+
context: documentMarkdown(editor),
|
|
187
|
+
blocks
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function start(task, prompt = "") {
|
|
191
|
+
if (!own.open) return Promise.resolve();
|
|
192
|
+
const request = {
|
|
193
|
+
task,
|
|
194
|
+
prompt,
|
|
195
|
+
language: task.needsLanguage ? own.language : void 0
|
|
196
|
+
};
|
|
197
|
+
update({ request });
|
|
198
|
+
return generate(request);
|
|
199
|
+
}
|
|
200
|
+
const submitPrompt = () => own.prompt.trim() ? start(store.get().freePrompt, own.prompt) : Promise.resolve();
|
|
201
|
+
const retry = () => own.request ? generate(own.request) : Promise.resolve();
|
|
202
|
+
function apply(action) {
|
|
203
|
+
const editor = getEditor();
|
|
204
|
+
const captured = anchor;
|
|
205
|
+
if (!editor || !captured || !store.get().canApply) return;
|
|
206
|
+
const outcome = applyAiResult(editor, action, captured, store.get().output);
|
|
207
|
+
if (outcome !== "ok") {
|
|
208
|
+
notify(aiApplyFailure(outcome, t));
|
|
209
|
+
if (outcome !== "unchanged") return;
|
|
210
|
+
}
|
|
211
|
+
close(false);
|
|
212
|
+
}
|
|
213
|
+
function insertAtCaret() {
|
|
214
|
+
const editor = getEditor();
|
|
215
|
+
const captured = anchor;
|
|
216
|
+
if (!editor || !captured || !store.get().canApply) return;
|
|
217
|
+
if (!insertAtCaretLine(editor, captured, store.get().output)) {
|
|
218
|
+
notify({
|
|
219
|
+
title: t("ai.apply.missing.title"),
|
|
220
|
+
description: t("ai.menu.missing.insert"),
|
|
221
|
+
kind: "error"
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
close(false);
|
|
226
|
+
}
|
|
227
|
+
function close(restoreSelection) {
|
|
228
|
+
const captured = anchor;
|
|
229
|
+
if (!captured) return;
|
|
230
|
+
const caret = own.mode === "caret";
|
|
231
|
+
anchor = null;
|
|
232
|
+
actionController?.abort();
|
|
233
|
+
actionController = null;
|
|
234
|
+
update({
|
|
235
|
+
open: false,
|
|
236
|
+
mode: "selection",
|
|
237
|
+
hasTextBefore: false,
|
|
238
|
+
prompt: "",
|
|
239
|
+
request: null,
|
|
240
|
+
runningAction: null
|
|
241
|
+
});
|
|
242
|
+
assistant.reset();
|
|
243
|
+
const editor = getEditor();
|
|
244
|
+
if (!editor || editor.isDestroyed) return;
|
|
245
|
+
hideAiSelectionHighlight(editor);
|
|
246
|
+
const resolved = restoreSelection ? resolveRange(editor.state, captured) : null;
|
|
247
|
+
releaseRanges(editor, [captured]);
|
|
248
|
+
if (resolved && (caret || resolved.from < resolved.to)) {
|
|
249
|
+
editor.view.dispatch(editor.state.tr.setSelection(TextSelection.create(editor.state.doc, resolved.from, resolved.to)));
|
|
250
|
+
editor.commands.focus();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function dispose() {
|
|
254
|
+
if (disposed) return;
|
|
255
|
+
disposed = true;
|
|
256
|
+
close(false);
|
|
257
|
+
listened?.off("transaction", onTransaction);
|
|
258
|
+
stopAssistant();
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
state: store,
|
|
262
|
+
offersSelection,
|
|
263
|
+
openForSelection,
|
|
264
|
+
openAtCaret,
|
|
265
|
+
range,
|
|
266
|
+
menuActions,
|
|
267
|
+
runAction,
|
|
268
|
+
setPrompt: (prompt) => update({ prompt }),
|
|
269
|
+
setLanguage: (language) => update({ language }),
|
|
270
|
+
start,
|
|
271
|
+
submitPrompt,
|
|
272
|
+
retry,
|
|
273
|
+
abort: assistant.abort,
|
|
274
|
+
apply,
|
|
275
|
+
insertAtCaret,
|
|
276
|
+
close,
|
|
277
|
+
dispose
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
export { AI_SELECTION_MENU_KEY, createAiSelectionFlow };
|
|
282
|
+
|
|
283
|
+
//# sourceMappingURL=selection-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-flow.js","names":[],"sources":["../../../src/features/ai/selection-flow.ts"],"sourcesContent":["import type { Editor, EditorEvents } from '@tiptap/core'\nimport { TextSelection } from '@tiptap/pm/state'\nimport { abilitiesAtSelection, aiContextOf, captureRange, type DocAnchor, hasAbility, releaseRanges, resolveRange, selectionRange } from '../../core'\nimport type { Notifier } from '../../core/notify'\nimport { ABILITY } from '../../core/schema'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport type { AiLanguage } from '../../settings'\nimport type { Translate } from '../../i18n'\nimport { aiApplyFailure, type AiResultAction, applyAiResult, documentMarkdown, insertAtCaretLine, rangeMarkdown } from './ai-apply'\nimport { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, type AiMenuAction, aiMenuActionsOf } from './ai-menu'\nimport { hideAiSelectionHighlight, showAiSelectionHighlight } from './ai-selection-highlight'\nimport type { AiAssistant, AiStatus } from './assistant'\nimport { AI_LIMITS, AI_TASKS, type AiTask } from './protocol'\n\n/** Plugin key of the bubble menu that offers the AI menu; the flow hides that bubble when it opens on the selection. */\nexport const AI_SELECTION_MENU_KEY = 'aiSelectionMenu'\n\n/** 'selection': rewrites the captured selection; 'caret': writes new text at the captured (collapsed) caret. */\nexport type AiSelectionMode = 'selection' | 'caret'\n\n/** What the menu was asked to run; „Erneut versuchen“ repeats it. `language` only for tasks that `needsLanguage`. */\nexport interface AiSelectionRequest {\n task: AiTask\n prompt: string\n language?: AiLanguage | undefined\n}\n\n/** Everything a surface renders of the menu: its own state plus the assistant's streaming state. */\nexport interface AiSelectionState {\n /** The menu is open on a captured range (selection) or caret. */\n open: boolean\n mode: AiSelectionMode\n /** Caret mode: whether the document has text before the caret line, for tasks that continue it (`needsSelection`). */\n hasTextBefore: boolean\n /** The quick actions of the registry for `mode`, in registry order. */\n actions: readonly AiTask[]\n /** The task the free prompt input starts (`custom` for a selection, `generate` at the caret). */\n freePrompt: AiTask\n /** The free prompt input's text; cleared when the menu closes. */\n prompt: string\n /** Target language of „Übersetzen“: the default each time the menu opens, changeable per call. */\n language: AiLanguage\n request: AiSelectionRequest | null\n /** Id of the contributed menu action (`AiMenuAction`) that runs right now. */\n runningAction: string | null\n status: AiStatus\n /** The result streamed so far. */\n output: string\n error: string | null\n /** The task list is shown: nothing was requested yet, or the request ended without text (rate limit). */\n picking: boolean\n /** The result is finished and non-blank: it may be applied. */\n canApply: boolean\n}\n\nexport interface AiSelectionFlowOptions {\n assistant: AiAssistant\n notify: Notifier\n /** The editor's translator (`FeatureContext.t`) for the notices the flow raises. */\n t: Translate\n /**\n * The current editor, read when a step runs, so a flow that continues after an await sees the latest one. The flow\n * listens to the transactions of the editor present when it is created: create a new flow for another instance.\n */\n editor: () => Editor | undefined\n /** The translation target language to start from, read each time the menu opens (the settings' default). */\n language: () => AiLanguage\n}\n\nexport interface AiSelectionFlow {\n state: ReadonlyStore<AiSelectionState>\n /**\n * Whether the bubble entry that opens the menu may show now: the menu is closed, the editor editable, the selection\n * holds text and rewriting is allowed there. The surface adds its own focus condition (DOM) on top.\n */\n offersSelection: () => boolean\n /**\n * Opens the menu on the selection: captures it (it survives edits made while the menu is open), highlights it and hides\n * the bubble entry. Nothing happens while the menu is open, the editor is read-only or the selection is empty.\n */\n openForSelection: () => void\n /**\n * Opens the menu at the caret in caret mode. Nothing happens while the menu is open, the editor is read-only or\n * generating is not allowed at the caret.\n */\n openAtCaret: () => void\n /** The captured range at its current positions (a caret: collapsed); null while closed or when the text is gone. */\n range: () => { from: number, to: number } | null\n /** The extra rows other features contribute (`aiMenuActions`) that apply to the captured range right now. */\n menuActions: () => AiMenuAction[]\n /**\n * Runs a contributed action on the captured range; the menu closes afterwards, unless it was closed or reopened\n * meanwhile. One action at a time. Closing the menu aborts the action's `signal`. A rejection of the action\n * propagates and leaves the menu open.\n */\n runAction: (action: AiMenuAction) => Promise<void>\n setPrompt: (text: string) => void\n setLanguage: (language: AiLanguage) => void\n /**\n * Runs `task` (with `prompt` for a free instruction) on the current text of the captured range and the current\n * document, streaming the result into `state.output`. The returned promise settles when the run ended; a stopped,\n * failed or rate-limited run leaves its outcome in `state` (a 429 also raises the assistant's notice). Without an\n * open menu nothing happens.\n */\n start: (task: AiTask, prompt?: string) => Promise<void>\n /** Starts the free prompt of the mode with `state.prompt`; nothing happens while it is blank. */\n submitPrompt: () => Promise<void>\n /** „Erneut versuchen“: runs the last request again on the range's and document's current text. */\n retry: () => Promise<void>\n /** Stops the streaming run; the partial result stays and can only be discarded. */\n abort: () => void\n /**\n * „Ersetzen“ opens the in-document review of the result over the captured range (nothing is written until it is\n * accepted there), „Darunter einfügen“ inserts it below. Closes the menu on success (and when the result equals the\n * text); otherwise notifies why not and stays open. Only a finished, non-blank result is applied.\n */\n apply: (action: AiResultAction) => void\n /** Caret mode „Einfügen“: inserts the result at the captured caret line and closes the menu; notifies when the caret is gone. */\n insertAtCaret: () => void\n /**\n * Closes the menu: stops the run and a contributed action, drops request and prompt, removes the highlight and\n * releases the captured range. `restoreSelection` selects that range (the caret in caret mode) again, so the\n * editor is where it was before. Does nothing while closed.\n */\n close: (restoreSelection: boolean) => void\n /** Closes the menu and stops listening to the editor and the assistant. Idempotent; the flow does not open again. */\n dispose: () => void\n}\n\n// Registry order; the free prompt (`custom`) gets its own input below the list.\nconst SELECTION_ACTIONS = Object.values(AI_TASKS).filter(task => task.selectionMenu && !task.needsPrompt)\nconst CARET_ACTIONS = Object.values(AI_TASKS).filter(task => task.caretMenu)\n\ninterface Own {\n open: boolean\n mode: AiSelectionMode\n hasTextBefore: boolean\n prompt: string\n language: AiLanguage\n request: AiSelectionRequest | null\n runningAction: string | null\n}\n\n/**\n * The flow of the AI quick-action menu on a selection or at the caret, shared by both UI surfaces: they render\n * `state` (and the preview of `state.output`), place the popover and call this. The captured range and the result's\n * apply target are DocAnchors, so a result still lands on the captured text after the document changed elsewhere.\n * Listens to the editor's transactions for `openAiMenuForSelection` / `openAiMenuAtCaret` requests.\n */\nexport function createAiSelectionFlow({ assistant, notify, t, editor: getEditor, language: defaultLanguage }: AiSelectionFlowOptions): AiSelectionFlow {\n /** The captured range; null while closed. Compared by identity to tell whether the menu was reopened meanwhile. */\n let anchor: DocAnchor | null = null\n let actionController: AbortController | null = null\n let disposed = false\n\n let own: Own = { open: false, mode: 'selection', hasTextBefore: false, prompt: '', language: defaultLanguage(), request: null, runningAction: null }\n\n function compose(): AiSelectionState {\n const { status, output, error } = assistant.state.get()\n const caret = own.mode === 'caret'\n return {\n ...own,\n actions: caret ? CARET_ACTIONS : SELECTION_ACTIONS,\n freePrompt: caret ? AI_TASKS.generate : AI_TASKS.custom,\n status,\n output,\n error,\n picking: status === 'idle' && !output,\n canApply: status === 'done' && !!output.trim(),\n }\n }\n\n const store = createStore<AiSelectionState>(compose())\n const stopAssistant = assistant.state.subscribe(() => store.set(compose()))\n\n function update(patch: Partial<Own>) {\n own = { ...own, ...patch }\n store.set(compose())\n }\n\n const range = () => {\n const editor = getEditor()\n return editor && !editor.isDestroyed && anchor ? resolveRange(editor.state, anchor) : null\n }\n\n function offersSelection(): boolean {\n const editor = getEditor()\n if (!editor || own.open || !editor.isEditable) return false\n const { selection, doc } = editor.state\n if (selection.empty || doc.textBetween(selection.from, selection.to, ' ').trim().length === 0) return false\n return abilitiesAtSelection(editor).has(ABILITY.aiRewrite)\n }\n\n function openForSelection() {\n const editor = getEditor()\n if (disposed || !editor || own.open || !editor.isEditable) return\n const selected = selectionRange(editor.state)\n if (selected.from >= selected.to) return\n const captured = captureRange(editor, selected)\n anchor = captured\n update({ open: true, mode: 'selection', hasTextBefore: false, prompt: '', language: defaultLanguage(), request: null })\n showAiSelectionHighlight(editor, captured)\n editor.view.dispatch(editor.state.tr.setMeta(AI_SELECTION_MENU_KEY, 'hide'))\n }\n\n function openAtCaret() {\n const editor = getEditor()\n if (disposed || !editor || own.open || !editor.isEditable || !hasAbility(editor, ABILITY.aiGenerate)) return\n const { $head, head } = editor.state.selection\n const hasTextBefore = editor.state.doc.textBetween(0, $head.depth ? $head.before() : head, ' ', ' ').trim().length > 0\n anchor = captureRange(editor, { from: head, to: head })\n update({ open: true, mode: 'caret', hasTextBefore, prompt: '', language: defaultLanguage(), request: null })\n }\n\n function onTransaction({ transaction }: EditorEvents['transaction']) {\n if (transaction.getMeta(AI_MENU_AT_CARET)) openAtCaret()\n // Requested by `openAiMenuForSelection` (the „Mit KI bearbeiten“ block action); a menu already open stays as it is.\n if (transaction.getMeta(AI_MENU_FOR_SELECTION)) openForSelection()\n }\n const listened = getEditor()\n listened?.on('transaction', onTransaction)\n\n function menuActions(): AiMenuAction[] {\n const editor = getEditor()\n const resolved = range()\n return editor && resolved ? aiMenuActionsOf(editor).filter(action => action.available?.(editor, resolved) ?? true) : []\n }\n\n async function runAction(action: AiMenuAction) {\n const editor = getEditor()\n const captured = anchor\n const resolved = range()\n if (!editor || !captured || !resolved || actionController) return\n const controller = new AbortController()\n actionController = controller\n update({ runningAction: action.id })\n try {\n await action.run(editor, resolved, controller.signal)\n }\n finally {\n // A close (or a reopen) meanwhile already reset it.\n if (actionController === controller) {\n actionController = null\n update({ runningAction: null })\n }\n }\n if (anchor === captured) close(false)\n }\n\n /** Runs `request` on the current text of the captured range, with the current document as context. */\n async function generate(request: AiSelectionRequest) {\n const editor = getEditor()\n const captured = anchor\n if (!editor || !captured) return\n const at = resolveRange(editor.state, captured)\n if (own.mode === 'caret') {\n // Tasks with `needsSelection` (continue) get the document up to the caret line as `selection` and the rest as\n // `context`; the others the whole document as context.\n if (!at) {\n notify({ title: t('ai.apply.missing.title'), description: t('ai.menu.missing.line'), kind: 'error' })\n return\n }\n const { doc } = editor.state\n const $at = doc.resolve(at.from)\n const splitBefore = $at.depth ? $at.before() : at.from\n const splitAfter = $at.depth ? $at.after() : at.from\n const continues = request.task.needsSelection\n const { blocks } = aiContextOf(editor)\n await assistant.run({\n task: request.task.id,\n prompt: request.prompt,\n selection: continues ? rangeMarkdown(editor, { from: 0, to: splitBefore }).slice(-AI_LIMITS.selection) : '',\n context: continues ? rangeMarkdown(editor, { from: splitAfter, to: doc.content.size }).slice(0, AI_LIMITS.context) : documentMarkdown(editor),\n blocks,\n })\n return\n }\n if (!at || at.from >= at.to) {\n notify({ title: t('ai.apply.missing.title'), description: t('ai.menu.missing.selection'), kind: 'error' })\n return\n }\n const { blocks } = aiContextOf(editor)\n await assistant.run({\n task: request.task.id,\n prompt: request.prompt,\n ...(request.language && { language: request.language }),\n selection: rangeMarkdown(editor, at),\n context: documentMarkdown(editor),\n blocks,\n })\n }\n\n function start(task: AiTask, prompt = ''): Promise<void> {\n if (!own.open) return Promise.resolve()\n const request: AiSelectionRequest = { task, prompt, language: task.needsLanguage ? own.language : undefined }\n update({ request })\n return generate(request)\n }\n\n const submitPrompt = () => own.prompt.trim() ? start(store.get().freePrompt, own.prompt) : Promise.resolve()\n\n const retry = () => own.request ? generate(own.request) : Promise.resolve()\n\n function apply(action: AiResultAction) {\n const editor = getEditor()\n const captured = anchor\n if (!editor || !captured || !store.get().canApply) return\n const outcome = applyAiResult(editor, action, captured, store.get().output)\n if (outcome !== 'ok') {\n notify(aiApplyFailure(outcome, t))\n if (outcome !== 'unchanged') return\n }\n close(false)\n }\n\n function insertAtCaret() {\n const editor = getEditor()\n const captured = anchor\n if (!editor || !captured || !store.get().canApply) return\n if (!insertAtCaretLine(editor, captured, store.get().output)) {\n notify({ title: t('ai.apply.missing.title'), description: t('ai.menu.missing.insert'), kind: 'error' })\n return\n }\n close(false)\n }\n\n function close(restoreSelection: boolean) {\n const captured = anchor\n if (!captured) return\n const caret = own.mode === 'caret'\n anchor = null\n actionController?.abort()\n actionController = null\n update({ open: false, mode: 'selection', hasTextBefore: false, prompt: '', request: null, runningAction: null })\n assistant.reset()\n const editor = getEditor()\n if (!editor || editor.isDestroyed) return\n hideAiSelectionHighlight(editor)\n const resolved = restoreSelection ? resolveRange(editor.state, captured) : null\n releaseRanges(editor, [captured])\n if (resolved && (caret || resolved.from < resolved.to)) {\n editor.view.dispatch(editor.state.tr.setSelection(TextSelection.create(editor.state.doc, resolved.from, resolved.to)))\n editor.commands.focus()\n }\n }\n\n function dispose() {\n if (disposed) return\n disposed = true\n close(false)\n listened?.off('transaction', onTransaction)\n stopAssistant()\n }\n\n return {\n state: store,\n offersSelection,\n openForSelection,\n openAtCaret,\n range,\n menuActions,\n runAction,\n setPrompt: prompt => update({ prompt }),\n setLanguage: language => update({ language }),\n start,\n submitPrompt,\n retry,\n abort: assistant.abort,\n apply,\n insertAtCaret,\n close,\n dispose,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AiAssistant, AiRunBody, AiStatus } from "./assistant.js";
|
|
2
|
-
import { MaybeRefOrGetter } from "vue";
|
|
3
2
|
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { MaybeRefOrGetter } from "vue";
|
|
4
4
|
//#region src/features/ai/use-ai-assistant.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Streams `/api/ai/generate` into Vue refs on the task's model slot: a thin hook over `createAiAssistant`. Call it
|
|
@@ -3,8 +3,8 @@ import "./protocol/index.js";
|
|
|
3
3
|
import { AgentScope } from "./agent-ops.js";
|
|
4
4
|
import { AgentOpState } from "./agent-op-review.js";
|
|
5
5
|
import { DocumentChat, DocumentChatGeneration, DocumentChatTurn } from "./document-chat-model.js";
|
|
6
|
-
import { MaybeRefOrGetter } from "vue";
|
|
7
6
|
import { Editor } from "@tiptap/core";
|
|
7
|
+
import { MaybeRefOrGetter } from "vue";
|
|
8
8
|
//#region src/features/ai/use-document-chat.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* The document chat (see `createDocumentChat`) as Vue refs: a thin hook. Call it during component setup (it injects
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { documentAuthors } from "../schema.js";
|
|
1
2
|
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
2
3
|
import { useEditorSettings } from "../../../vue/settings.js";
|
|
3
4
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
4
5
|
import { currentAuthor } from "../attribution.js";
|
|
5
|
-
import { documentAuthors } from "../schema.js";
|
|
6
6
|
import { authorStyleItems } from "../author-color-styles.js";
|
|
7
7
|
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, renderList, toDisplayString, unref } from "vue";
|
|
8
8
|
//#region src/features/authors/components/AuthorColorsPanel.vue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { derived } from "../../core/store.js";
|
|
2
|
-
import { AuthorAttribution } from "./attribution.js";
|
|
3
2
|
import { AuthorMark } from "./schema.js";
|
|
3
|
+
import { AuthorAttribution } from "./attribution.js";
|
|
4
4
|
//#region src/features/authors/feature.ts
|
|
5
5
|
/**
|
|
6
6
|
* Who wrote what: every text run the session's user inserts carries an `author` mark (id, name, colour; one author per
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AUTHOR_COLOR_STYLES } from "../../settings/values.js";
|
|
2
|
-
import { AuthorAttribution, authorAttributionKey, authorAttributionPlugin, currentAuthor } from "./attribution.js";
|
|
3
2
|
import { AuthorMark, authorsSchemaExtensions, documentAuthors } from "./schema.js";
|
|
3
|
+
import { AuthorAttribution, authorAttributionKey, authorAttributionPlugin, currentAuthor } from "./attribution.js";
|
|
4
4
|
import { authorsFeatureCore } from "./feature.js";
|
|
5
5
|
import { authorStyleItems } from "./author-color-styles.js";
|
|
6
6
|
export { AUTHOR_COLOR_STYLES, AuthorAttribution, AuthorMark, authorAttributionKey, authorAttributionPlugin, authorStyleItems, authorsFeatureCore, authorsSchemaExtensions, currentAuthor, documentAuthors };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AUTHOR_COLOR_STYLES } from "../../../settings/values.js";
|
|
2
|
+
import { documentAuthors } from "../schema.js";
|
|
2
3
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
3
4
|
import { cn } from "../../../react/ui/utils.js";
|
|
4
5
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
5
6
|
import { Label } from "../../../react/ui/label.js";
|
|
6
7
|
import { Switch } from "../../../react/ui/switch.js";
|
|
7
8
|
import { currentAuthor } from "../attribution.js";
|
|
8
|
-
import { documentAuthors } from "../schema.js";
|
|
9
9
|
import { RadioGroup, RadioGroupItem } from "../../../react/ui/radio-group.js";
|
|
10
10
|
import { authorStyleItems } from "../author-color-styles.js";
|
|
11
11
|
import { useId } from "react";
|