@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,22 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { abilitiesAtSelection, hasAbility } from "../../../core/abilities.js";
|
|
3
|
-
import { aiContextOf, parseBlocks } from "../../../core/pipeline.js";
|
|
1
|
+
import { parseBlocks } from "../../../core/pipeline.js";
|
|
4
2
|
import { AI_LANGUAGES } from "../../../settings/values.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { resolveRange } from "../../../core/doc-anchors.js";
|
|
4
|
+
import { AI_LANGUAGE_KEYS } from "../protocol/models.js";
|
|
7
5
|
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
8
6
|
import { useEditorSettings } from "../../../vue/settings.js";
|
|
9
7
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { aiApplyFailure, applyAiResult, documentMarkdown, insertAtCaretLine, rangeMarkdown } from "../ai-apply.js";
|
|
13
|
-
import { aiMenuActionsOf } from "../ai-menu.js";
|
|
14
|
-
import { aiSelectionHighlightKey, hideAiSelectionHighlight, showAiSelectionHighlight } from "../ai-selection-highlight.js";
|
|
8
|
+
import { aiSelectionHighlightKey } from "../ai-selection-highlight.js";
|
|
9
|
+
import { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from "../selection-flow.js";
|
|
15
10
|
import { previewExtensions } from "../preview-extensions.js";
|
|
16
11
|
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
12
|
+
import { useStoreRef } from "../../../vue/store-ref.js";
|
|
17
13
|
import { useAiAssistant } from "../use-ai-assistant.js";
|
|
18
14
|
import _sfc_main$1 from "./ModelQuickSwitch.js";
|
|
19
|
-
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, onBeforeUnmount, openBlock,
|
|
15
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, onBeforeUnmount, openBlock, renderList, toDisplayString, unref, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
20
16
|
import { EditorContent, useEditor } from "@tiptap/vue-3";
|
|
21
17
|
import { BubbleMenu } from "@tiptap/vue-3/menus";
|
|
22
18
|
//#region src/features/ai/components/AiSelectionMenu.vue
|
|
@@ -40,9 +36,9 @@ const _hoisted_9 = { class: "flex flex-wrap justify-end gap-2" };
|
|
|
40
36
|
* (`selectionMenu`), the result streams into a preview and is applied over the range captured when the menu opened.
|
|
41
37
|
* Caret mode (`/ai`, see `openAiMenuAtCaret`): the same popover opens at the caret with the `caretMenu` tasks and
|
|
42
38
|
* `generate` as free prompt; the result is inserted at the caret line (`insertAtCaretLine`).
|
|
43
|
-
* Needs the AiSelectionHighlight extension on the editor.
|
|
39
|
+
* Needs the AiSelectionHighlight extension on the editor. The flow (`createAiSelectionFlow`) owns every step; this
|
|
40
|
+
* component renders its state, places the popover and shows the preview.
|
|
44
41
|
*/
|
|
45
|
-
const MENU_KEY = "aiSelectionMenu";
|
|
46
42
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
47
43
|
__name: "AiSelectionMenu",
|
|
48
44
|
props: { editor: {} },
|
|
@@ -51,219 +47,52 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
51
47
|
const { Alert: UAlert, Button: UButton, Icon: UIcon, Input: UInput, Popover: UPopover, Select: USelect } = useUiKit();
|
|
52
48
|
const notify = useNotifier(() => props.editor);
|
|
53
49
|
const { t } = useEditorI18n();
|
|
54
|
-
const {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
50
|
+
const { model: assistant } = useAiAssistant(() => props.editor);
|
|
51
|
+
const { settings } = useEditorSettings();
|
|
52
|
+
const flow = createAiSelectionFlow({
|
|
53
|
+
assistant,
|
|
54
|
+
notify,
|
|
55
|
+
t,
|
|
56
|
+
editor: () => props.editor,
|
|
57
|
+
language: () => settings.value.ai.translateLanguage
|
|
58
|
+
});
|
|
59
|
+
const state = useStoreRef(flow.state);
|
|
57
60
|
const languageItems = computed(() => AI_LANGUAGES.map((l) => ({
|
|
58
61
|
label: t(AI_LANGUAGE_KEYS[l]),
|
|
59
62
|
value: l
|
|
60
63
|
})));
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const freePrompt = computed(() => mode.value === "caret" ? AI_TASKS.generate : custom);
|
|
70
|
-
/** Caret mode: whether the document has text before the caret line, for tasks that continue it (`needsSelection`). */
|
|
71
|
-
const hasTextBefore = ref(false);
|
|
64
|
+
const prompt = computed({
|
|
65
|
+
get: () => state.value.prompt,
|
|
66
|
+
set: flow.setPrompt
|
|
67
|
+
});
|
|
68
|
+
const language = computed({
|
|
69
|
+
get: () => state.value.language,
|
|
70
|
+
set: flow.setLanguage
|
|
71
|
+
});
|
|
72
72
|
const promptInput = useTemplateRef("promptInput");
|
|
73
|
-
/** The selection the menu works on, captured when it opened; null while closed. */
|
|
74
|
-
const range = shallowRef(null);
|
|
75
|
-
/** The current request; „Erneut versuchen“ repeats it. */
|
|
76
|
-
const request = shallowRef(null);
|
|
77
|
-
const picking = computed(() => status.value === "idle" && !output.value);
|
|
78
|
-
const canApply = computed(() => status.value === "done" && !!output.value.trim());
|
|
79
73
|
const requestLabel = computed(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
const request = state.value.request;
|
|
75
|
+
if (!request) return void 0;
|
|
76
|
+
const task = t(request.task.labelKey);
|
|
77
|
+
return request.language ? t("ai.menu.requestWithLanguage", {
|
|
83
78
|
task,
|
|
84
|
-
language: t(AI_LANGUAGE_KEYS[request.
|
|
79
|
+
language: t(AI_LANGUAGE_KEYS[request.language])
|
|
85
80
|
}) : task;
|
|
86
81
|
});
|
|
87
|
-
const menuActions = computed(() =>
|
|
88
|
-
|
|
89
|
-
return resolved ? aiMenuActionsOf(props.editor).filter((action) => action.available?.(props.editor, resolved) ?? true) : [];
|
|
90
|
-
});
|
|
91
|
-
const runningAction = ref(null);
|
|
92
|
-
let actionController = null;
|
|
93
|
-
async function runMenuAction(action) {
|
|
94
|
-
const captured = range.value;
|
|
95
|
-
const resolved = captured && resolveRange(props.editor.state, captured);
|
|
96
|
-
if (!resolved || runningAction.value) return;
|
|
97
|
-
const own = new AbortController();
|
|
98
|
-
actionController = own;
|
|
99
|
-
runningAction.value = action.id;
|
|
100
|
-
try {
|
|
101
|
-
await action.run(props.editor, resolved, own.signal);
|
|
102
|
-
} finally {
|
|
103
|
-
if (actionController === own) actionController = null;
|
|
104
|
-
runningAction.value = null;
|
|
105
|
-
}
|
|
106
|
-
if (range.value === captured) close(false);
|
|
107
|
-
}
|
|
108
|
-
watch(range, (value) => {
|
|
109
|
-
if (!value) {
|
|
110
|
-
actionController?.abort();
|
|
111
|
-
actionController = null;
|
|
112
|
-
} else language.value = settings.value.ai.translateLanguage;
|
|
113
|
-
});
|
|
114
|
-
const shouldShow = ({ editor, element, view, state, from, to }) => {
|
|
115
|
-
if (range.value || !editor.isEditable || state.selection.empty) return false;
|
|
116
|
-
if (!view.hasFocus() && !element.contains(document.activeElement)) return false;
|
|
117
|
-
if (state.doc.textBetween(from, to, " ").trim().length === 0) return false;
|
|
118
|
-
return abilitiesAtSelection(editor).has(ABILITY.aiRewrite);
|
|
119
|
-
};
|
|
120
|
-
function openMenu() {
|
|
121
|
-
const { editor } = props;
|
|
122
|
-
const selected = selectionRange(editor.state);
|
|
123
|
-
if (selected.from >= selected.to) return;
|
|
124
|
-
const captured = captureRange(editor, selected);
|
|
125
|
-
range.value = captured;
|
|
126
|
-
showAiSelectionHighlight(editor, captured);
|
|
127
|
-
editor.view.dispatch(editor.state.tr.setMeta(MENU_KEY, "hide"));
|
|
128
|
-
}
|
|
129
|
-
/** Opens the menu at the caret (requested by `openAiMenuAtCaret`, e.g. the `/ai` slash command). */
|
|
130
|
-
function openAtCaret() {
|
|
131
|
-
const { editor } = props;
|
|
132
|
-
if (range.value || !editor.isEditable) return;
|
|
133
|
-
const { $head, head } = editor.state.selection;
|
|
134
|
-
mode.value = "caret";
|
|
135
|
-
hasTextBefore.value = editor.state.doc.textBetween(0, $head.depth ? $head.before() : head, " ", " ").trim().length > 0;
|
|
136
|
-
range.value = captureRange(editor, {
|
|
137
|
-
from: head,
|
|
138
|
-
to: head
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
function onTransaction({ transaction }) {
|
|
142
|
-
if (transaction.getMeta("aiMenuAtCaret") && hasAbility(props.editor, ABILITY.aiGenerate)) openAtCaret();
|
|
143
|
-
if (transaction.getMeta("aiMenuForSelection") && !range.value && props.editor.isEditable) openMenu();
|
|
144
|
-
}
|
|
145
|
-
props.editor.on("transaction", onTransaction);
|
|
82
|
+
const menuActions = computed(() => state.value.open ? flow.menuActions() : []);
|
|
83
|
+
const shouldShow = ({ element, view }) => (view.hasFocus() || element.contains(document.activeElement)) && flow.offersSelection();
|
|
146
84
|
/** Caret mode: the prompt input gets the focus, so the instruction can be typed right away. */
|
|
147
85
|
function onOpenAutoFocus(event) {
|
|
148
|
-
if (
|
|
86
|
+
if (state.value.mode !== "caret") return;
|
|
149
87
|
event.preventDefault();
|
|
150
88
|
promptInput.value?.inputRef?.focus();
|
|
151
89
|
}
|
|
152
|
-
function start(task, options = {}) {
|
|
153
|
-
request.value = {
|
|
154
|
-
task,
|
|
155
|
-
prompt: options.prompt ?? "",
|
|
156
|
-
language: options.language
|
|
157
|
-
};
|
|
158
|
-
generate();
|
|
159
|
-
}
|
|
160
|
-
/** Runs the current request on the current text of the captured range, with the current document as context. */
|
|
161
|
-
function generate() {
|
|
162
|
-
const { editor } = props;
|
|
163
|
-
const current = request.value;
|
|
164
|
-
if (!range.value || !current) return;
|
|
165
|
-
if (mode.value === "caret") {
|
|
166
|
-
generateAtCaret(current);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const resolved = resolveRange(editor.state, range.value);
|
|
170
|
-
if (!resolved || resolved.from >= resolved.to) {
|
|
171
|
-
notify({
|
|
172
|
-
title: t("ai.apply.missing.title"),
|
|
173
|
-
description: t("ai.menu.missing.selection"),
|
|
174
|
-
kind: "error"
|
|
175
|
-
});
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const { blocks } = aiContextOf(editor);
|
|
179
|
-
run({
|
|
180
|
-
task: current.task.id,
|
|
181
|
-
prompt: current.prompt,
|
|
182
|
-
language: current.language,
|
|
183
|
-
selection: rangeMarkdown(editor, resolved),
|
|
184
|
-
context: documentMarkdown(editor),
|
|
185
|
-
blocks
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Caret mode: tasks with `needsSelection` (continue) get the document up to the caret line as `selection` and the rest
|
|
190
|
-
* as `context`; the others the whole document as context.
|
|
191
|
-
*/
|
|
192
|
-
function generateAtCaret(current) {
|
|
193
|
-
const { editor } = props;
|
|
194
|
-
const at = range.value && resolveRange(editor.state, range.value);
|
|
195
|
-
if (!at) {
|
|
196
|
-
notify({
|
|
197
|
-
title: t("ai.apply.missing.title"),
|
|
198
|
-
description: t("ai.menu.missing.line"),
|
|
199
|
-
kind: "error"
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const { doc } = editor.state;
|
|
204
|
-
const $at = doc.resolve(at.from);
|
|
205
|
-
const splitBefore = $at.depth ? $at.before() : at.from;
|
|
206
|
-
const splitAfter = $at.depth ? $at.after() : at.from;
|
|
207
|
-
const continues = current.task.needsSelection;
|
|
208
|
-
const { blocks } = aiContextOf(editor);
|
|
209
|
-
run({
|
|
210
|
-
task: current.task.id,
|
|
211
|
-
prompt: current.prompt,
|
|
212
|
-
selection: continues ? rangeMarkdown(editor, {
|
|
213
|
-
from: 0,
|
|
214
|
-
to: splitBefore
|
|
215
|
-
}).slice(-AI_LIMITS.selection) : "",
|
|
216
|
-
context: continues ? rangeMarkdown(editor, {
|
|
217
|
-
from: splitAfter,
|
|
218
|
-
to: doc.content.size
|
|
219
|
-
}).slice(0, AI_LIMITS.context) : documentMarkdown(editor),
|
|
220
|
-
blocks
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
/** 'replace' opens the in-document review of the result (AiReviewBar); nothing is written until accepted there. */
|
|
224
|
-
function apply(action) {
|
|
225
|
-
if (!range.value || !canApply.value) return;
|
|
226
|
-
const outcome = applyAiResult(props.editor, action, range.value, output.value);
|
|
227
|
-
if (outcome !== "ok") {
|
|
228
|
-
notify(aiApplyFailure(outcome, t));
|
|
229
|
-
if (outcome !== "unchanged") return;
|
|
230
|
-
}
|
|
231
|
-
close(false);
|
|
232
|
-
}
|
|
233
|
-
function insertAtCaret() {
|
|
234
|
-
if (!range.value || !canApply.value) return;
|
|
235
|
-
if (!insertAtCaretLine(props.editor, range.value, output.value)) {
|
|
236
|
-
notify({
|
|
237
|
-
title: t("ai.apply.missing.title"),
|
|
238
|
-
description: t("ai.menu.missing.insert"),
|
|
239
|
-
kind: "error"
|
|
240
|
-
});
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
close(false);
|
|
244
|
-
}
|
|
245
|
-
/** Closes the menu and removes the highlight; `restoreSelection` selects the captured range (caret mode: caret) again. */
|
|
246
|
-
function close(restoreSelection) {
|
|
247
|
-
const { editor } = props;
|
|
248
|
-
const captured = range.value;
|
|
249
|
-
const caret = mode.value === "caret";
|
|
250
|
-
reset();
|
|
251
|
-
request.value = null;
|
|
252
|
-
prompt.value = "";
|
|
253
|
-
range.value = null;
|
|
254
|
-
mode.value = "selection";
|
|
255
|
-
if (editor.isDestroyed) return;
|
|
256
|
-
hideAiSelectionHighlight(editor);
|
|
257
|
-
const resolved = captured && restoreSelection ? resolveRange(editor.state, captured) : null;
|
|
258
|
-
if (captured) releaseRanges(editor, [captured]);
|
|
259
|
-
if (resolved && (caret || resolved.from < resolved.to)) editor.chain().focus().setTextSelection(resolved).run();
|
|
260
|
-
}
|
|
261
90
|
let anchorRect = new DOMRect();
|
|
262
91
|
const reference = {
|
|
263
92
|
getBoundingClientRect() {
|
|
264
93
|
const { editor } = props;
|
|
265
94
|
if (editor.isDestroyed) return anchorRect;
|
|
266
|
-
if (
|
|
95
|
+
if (state.value.mode === "caret") return caretRect(editor);
|
|
267
96
|
const highlight = aiSelectionHighlightKey.getState(editor.state);
|
|
268
97
|
const highlighted = highlight && resolveRange(editor.state, highlight.anchor);
|
|
269
98
|
if (!highlighted || highlighted.from >= highlighted.to) return anchorRect;
|
|
@@ -285,7 +114,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
285
114
|
function caretRect(editor) {
|
|
286
115
|
if (caretAt?.state !== editor.state) caretAt = {
|
|
287
116
|
state: editor.state,
|
|
288
|
-
pos: range
|
|
117
|
+
pos: flow.range()?.from ?? null
|
|
289
118
|
};
|
|
290
119
|
if (caretAt.pos === null) return anchorRect;
|
|
291
120
|
const coords = editor.view.coordsAtPos(caretAt.pos);
|
|
@@ -298,7 +127,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
298
127
|
editorProps: { attributes: { class: "tiptap ai-selection-preview" } }
|
|
299
128
|
});
|
|
300
129
|
let previewTimer;
|
|
301
|
-
watch(output, () => {
|
|
130
|
+
watch(() => state.value.output, () => {
|
|
302
131
|
if (previewTimer) return;
|
|
303
132
|
previewTimer = setTimeout(() => {
|
|
304
133
|
previewTimer = void 0;
|
|
@@ -306,22 +135,19 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
306
135
|
if (!instance) return;
|
|
307
136
|
instance.commands.setContent({
|
|
308
137
|
type: "doc",
|
|
309
|
-
content: parseBlocks(props.editor,
|
|
138
|
+
content: parseBlocks(props.editor, state.value.output).blocks
|
|
310
139
|
}, { emitUpdate: false });
|
|
311
140
|
}, 80);
|
|
312
141
|
});
|
|
313
142
|
onBeforeUnmount(() => {
|
|
314
|
-
props.editor.off("transaction", onTransaction);
|
|
315
143
|
clearTimeout(previewTimer);
|
|
316
|
-
|
|
317
|
-
actionController?.abort();
|
|
318
|
-
if (!props.editor.isDestroyed) hideAiSelectionHighlight(props.editor);
|
|
144
|
+
flow.dispose();
|
|
319
145
|
preview.value?.destroy();
|
|
320
146
|
});
|
|
321
147
|
return (_ctx, _cache) => {
|
|
322
148
|
return openBlock(), createElementBlock(Fragment, null, [createVNode(unref(BubbleMenu), {
|
|
323
149
|
editor: __props.editor,
|
|
324
|
-
"plugin-key":
|
|
150
|
+
"plugin-key": unref(AI_SELECTION_MENU_KEY),
|
|
325
151
|
"should-show": shouldShow,
|
|
326
152
|
options: {
|
|
327
153
|
placement: "top",
|
|
@@ -335,13 +161,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
335
161
|
variant: "ghost",
|
|
336
162
|
icon: "i-lucide-sparkles",
|
|
337
163
|
label: unref(t)("ai.menu.trigger"),
|
|
338
|
-
onClick:
|
|
164
|
+
onClick: _cache[0] || (_cache[0] = ($event) => unref(flow).openForSelection())
|
|
339
165
|
}, null, 8, ["label"])])]),
|
|
340
166
|
_: 1
|
|
341
|
-
}, 8, ["editor"]), createVNode(unref(UPopover), {
|
|
342
|
-
open:
|
|
167
|
+
}, 8, ["editor", "plugin-key"]), createVNode(unref(UPopover), {
|
|
168
|
+
open: unref(state).open,
|
|
343
169
|
reference,
|
|
344
|
-
dismissible: picking
|
|
170
|
+
dismissible: unref(state).picking,
|
|
345
171
|
content: {
|
|
346
172
|
side: "bottom",
|
|
347
173
|
align: "start",
|
|
@@ -349,18 +175,18 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
349
175
|
onOpenAutoFocus
|
|
350
176
|
},
|
|
351
177
|
ui: { content: "w-[30rem] max-w-[calc(100vw-2rem)]" },
|
|
352
|
-
"onUpdate:open": _cache[
|
|
353
|
-
if (!value) close(mode
|
|
178
|
+
"onUpdate:open": _cache[10] || (_cache[10] = (value) => {
|
|
179
|
+
if (!value) unref(flow).close(unref(state).mode === "caret");
|
|
354
180
|
})
|
|
355
181
|
}, {
|
|
356
|
-
content: withCtx(() => [picking
|
|
182
|
+
content: withCtx(() => [unref(state).picking ? (openBlock(), createElementBlock("div", _hoisted_2, [
|
|
357
183
|
createCommentVNode(" Caret mode generates blocks, selection mode rewrites text in the block: each has its own model slot. "),
|
|
358
184
|
createElementVNode("div", _hoisted_3, [createElementVNode("span", _hoisted_4, toDisplayString(unref(t)("ai.menu.model")), 1), createVNode(_sfc_main$1, {
|
|
359
|
-
slot: mode
|
|
185
|
+
slot: unref(state).mode === "caret" ? "generate" : "rewrite",
|
|
360
186
|
size: "xs",
|
|
361
187
|
class: "w-56"
|
|
362
188
|
}, null, 8, ["slot"])]),
|
|
363
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(actions
|
|
189
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(state).actions, (task) => {
|
|
364
190
|
return openBlock(), createElementBlock("div", {
|
|
365
191
|
key: task.id,
|
|
366
192
|
class: "flex items-center gap-1"
|
|
@@ -370,8 +196,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
370
196
|
color: "neutral",
|
|
371
197
|
variant: "ghost",
|
|
372
198
|
class: "flex-1 justify-start",
|
|
373
|
-
disabled: mode
|
|
374
|
-
onClick: ($event) => start(task
|
|
199
|
+
disabled: unref(state).mode === "caret" && task.needsSelection && !unref(state).hasTextBefore,
|
|
200
|
+
onClick: ($event) => unref(flow).start(task)
|
|
375
201
|
}, null, 8, [
|
|
376
202
|
"icon",
|
|
377
203
|
"label",
|
|
@@ -380,7 +206,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
380
206
|
]), task.needsLanguage ? (openBlock(), createBlock(unref(USelect), {
|
|
381
207
|
key: 0,
|
|
382
208
|
modelValue: language.value,
|
|
383
|
-
"onUpdate:modelValue": _cache[
|
|
209
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => language.value = $event),
|
|
384
210
|
items: languageItems.value,
|
|
385
211
|
size: "sm",
|
|
386
212
|
class: "w-36",
|
|
@@ -399,9 +225,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
399
225
|
color: "neutral",
|
|
400
226
|
variant: "ghost",
|
|
401
227
|
class: "justify-start",
|
|
402
|
-
loading: runningAction
|
|
403
|
-
disabled: runningAction
|
|
404
|
-
onClick: ($event) =>
|
|
228
|
+
loading: unref(state).runningAction === action.id,
|
|
229
|
+
disabled: unref(state).runningAction !== null && unref(state).runningAction !== action.id,
|
|
230
|
+
onClick: ($event) => unref(flow).runAction(action)
|
|
405
231
|
}, null, 8, [
|
|
406
232
|
"icon",
|
|
407
233
|
"label",
|
|
@@ -412,14 +238,14 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
412
238
|
}), 128)),
|
|
413
239
|
createElementVNode("form", {
|
|
414
240
|
class: "mt-1 flex gap-1 border-t border-[var(--sk-border)] pt-2",
|
|
415
|
-
onSubmit: _cache[
|
|
241
|
+
onSubmit: _cache[3] || (_cache[3] = withModifiers(($event) => unref(flow).submitPrompt(), ["prevent"]))
|
|
416
242
|
}, [createVNode(unref(UInput), {
|
|
417
243
|
ref_key: "promptInput",
|
|
418
244
|
ref: promptInput,
|
|
419
245
|
modelValue: prompt.value,
|
|
420
|
-
"onUpdate:modelValue": _cache[
|
|
421
|
-
icon: freePrompt.
|
|
422
|
-
placeholder: freePrompt.
|
|
246
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => prompt.value = $event),
|
|
247
|
+
icon: unref(state).freePrompt.icon,
|
|
248
|
+
placeholder: unref(state).freePrompt.promptLabelKey && unref(t)(unref(state).freePrompt.promptLabelKey),
|
|
423
249
|
class: "flex-1"
|
|
424
250
|
}, null, 8, [
|
|
425
251
|
"modelValue",
|
|
@@ -428,48 +254,48 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
428
254
|
]), createVNode(unref(UButton), {
|
|
429
255
|
type: "submit",
|
|
430
256
|
icon: "i-lucide-arrow-up",
|
|
431
|
-
disabled: !prompt.
|
|
257
|
+
disabled: !unref(state).prompt.trim(),
|
|
432
258
|
"aria-label": unref(t)("ai.menu.sendInstruction")
|
|
433
259
|
}, null, 8, ["disabled", "aria-label"])], 32)
|
|
434
260
|
])) : (openBlock(), createElementBlock("div", _hoisted_5, [
|
|
435
261
|
createElementVNode("div", _hoisted_6, [
|
|
436
|
-
request
|
|
262
|
+
unref(state).request ? (openBlock(), createBlock(unref(UIcon), {
|
|
437
263
|
key: 0,
|
|
438
|
-
name: request.
|
|
264
|
+
name: unref(state).request.task.icon,
|
|
439
265
|
class: "size-4"
|
|
440
266
|
}, null, 8, ["name"])) : createCommentVNode("v-if", true),
|
|
441
267
|
createElementVNode("span", _hoisted_7, toDisplayString(requestLabel.value), 1),
|
|
442
|
-
unref(status
|
|
268
|
+
unref(state).status === "streaming" ? (openBlock(), createBlock(unref(UIcon), {
|
|
443
269
|
key: 1,
|
|
444
270
|
name: "i-lucide-loader-circle",
|
|
445
271
|
class: "size-4 animate-spin text-[var(--sk-muted)]"
|
|
446
272
|
})) : createCommentVNode("v-if", true)
|
|
447
273
|
]),
|
|
448
|
-
unref(error
|
|
274
|
+
unref(state).error ? (openBlock(), createBlock(unref(UAlert), {
|
|
449
275
|
key: 0,
|
|
450
276
|
color: "error",
|
|
451
277
|
variant: "subtle",
|
|
452
|
-
title: unref(error
|
|
278
|
+
title: unref(state).error
|
|
453
279
|
}, null, 8, ["title"])) : createCommentVNode("v-if", true),
|
|
454
280
|
createElementVNode("div", _hoisted_8, [unref(preview) ? (openBlock(), createBlock(unref(EditorContent), {
|
|
455
281
|
key: 0,
|
|
456
282
|
editor: unref(preview)
|
|
457
283
|
}, null, 8, ["editor"])) : createCommentVNode("v-if", true)]),
|
|
458
284
|
createElementVNode("div", _hoisted_9, [
|
|
459
|
-
unref(status
|
|
285
|
+
unref(state).status === "streaming" ? (openBlock(), createBlock(unref(UButton), {
|
|
460
286
|
key: 0,
|
|
461
287
|
size: "sm",
|
|
462
288
|
color: "neutral",
|
|
463
289
|
variant: "ghost",
|
|
464
290
|
label: unref(t)("ai.menu.cancel"),
|
|
465
|
-
onClick: _cache[
|
|
291
|
+
onClick: _cache[4] || (_cache[4] = ($event) => unref(flow).abort())
|
|
466
292
|
}, null, 8, ["label"])) : createCommentVNode("v-if", true),
|
|
467
293
|
createVNode(unref(UButton), {
|
|
468
294
|
size: "sm",
|
|
469
295
|
color: "neutral",
|
|
470
296
|
variant: "ghost",
|
|
471
297
|
label: unref(t)("ai.menu.discard"),
|
|
472
|
-
onClick: _cache[
|
|
298
|
+
onClick: _cache[5] || (_cache[5] = ($event) => unref(flow).close(true))
|
|
473
299
|
}, null, 8, ["label"]),
|
|
474
300
|
createVNode(unref(UButton), {
|
|
475
301
|
size: "sm",
|
|
@@ -477,30 +303,30 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
477
303
|
variant: "outline",
|
|
478
304
|
icon: "i-lucide-rotate-ccw",
|
|
479
305
|
label: unref(t)("ai.menu.retry"),
|
|
480
|
-
disabled: unref(status
|
|
481
|
-
onClick:
|
|
306
|
+
disabled: unref(state).status === "streaming",
|
|
307
|
+
onClick: _cache[6] || (_cache[6] = ($event) => unref(flow).retry())
|
|
482
308
|
}, null, 8, ["label", "disabled"]),
|
|
483
|
-
mode
|
|
309
|
+
unref(state).mode === "caret" ? (openBlock(), createBlock(unref(UButton), {
|
|
484
310
|
key: 1,
|
|
485
311
|
size: "sm",
|
|
486
312
|
icon: "i-lucide-text-cursor-input",
|
|
487
313
|
label: unref(t)("ai.menu.insert"),
|
|
488
|
-
disabled: !canApply
|
|
489
|
-
onClick: insertAtCaret
|
|
314
|
+
disabled: !unref(state).canApply,
|
|
315
|
+
onClick: _cache[7] || (_cache[7] = ($event) => unref(flow).insertAtCaret())
|
|
490
316
|
}, null, 8, ["label", "disabled"])) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createVNode(unref(UButton), {
|
|
491
317
|
size: "sm",
|
|
492
318
|
color: "neutral",
|
|
493
319
|
variant: "outline",
|
|
494
320
|
icon: "i-lucide-list-plus",
|
|
495
321
|
label: unref(t)("ai.menu.insertBelow"),
|
|
496
|
-
disabled: !canApply
|
|
497
|
-
onClick: _cache[
|
|
322
|
+
disabled: !unref(state).canApply,
|
|
323
|
+
onClick: _cache[8] || (_cache[8] = ($event) => unref(flow).apply("insert-below"))
|
|
498
324
|
}, null, 8, ["label", "disabled"]), createVNode(unref(UButton), {
|
|
499
325
|
size: "sm",
|
|
500
326
|
icon: "i-lucide-replace",
|
|
501
327
|
label: unref(t)("ai.menu.replace"),
|
|
502
|
-
disabled: !canApply
|
|
503
|
-
onClick: _cache[
|
|
328
|
+
disabled: !unref(state).canApply,
|
|
329
|
+
onClick: _cache[9] || (_cache[9] = ($event) => unref(flow).apply("replace"))
|
|
504
330
|
}, null, 8, ["label", "disabled"])], 64))
|
|
505
331
|
])
|
|
506
332
|
]))]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiSelectionMenu.js","names":["_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_withCtx","_createElementVNode","_createCommentVNode","_toDisplayString","ModelQuickSwitch","_renderList","_createBlock","_withModifiers"],"sources":["../../../../src/features/ai/components/AiSelectionMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'\nimport type { EditorState } from '@tiptap/pm/state'\nimport { type Editor, EditorContent, type EditorEvents, useEditor } from '@tiptap/vue-3'\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\nimport { computed, onBeforeUnmount, ref, shallowRef, useTemplateRef, watch } from 'vue'\nimport { abilitiesAtSelection, aiContextOf, captureRange, type DocAnchor, hasAbility, parseBlocks, releaseRanges, resolveRange, selectionRange } from '../../../core'\nimport { useEditorSettings } from '../../../vue/settings'\nimport type { InputExposed } from '../../../vue/kit/types'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useNotifier } from '../../../vue/use-notifier'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { ABILITY } from '../../../core/schema'\nimport { AI_LANGUAGES, type AiLanguage } from '../../../settings'\nimport { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, type AiMenuAction, aiMenuActionsOf } from '../ai-menu'\nimport { aiApplyFailure, type AiResultAction, applyAiResult, documentMarkdown, insertAtCaretLine, rangeMarkdown } from '../ai-apply'\nimport { aiSelectionHighlightKey, hideAiSelectionHighlight, showAiSelectionHighlight } from '../ai-selection-highlight'\nimport { previewExtensions } from '../preview-extensions'\nimport { AI_LANGUAGE_KEYS, AI_LIMITS, AI_TASKS, type AiTask } from '../protocol'\nimport { useAiAssistant } from '../use-ai-assistant'\nimport ModelQuickSwitch from './ModelQuickSwitch.vue'\n\n/**\n * AI quick actions on a text selection: a bubble menu entry opens a popover with the selection tasks of the registry\n * (`selectionMenu`), the result streams into a preview and is applied over the range captured when the menu opened.\n * Caret mode (`/ai`, see `openAiMenuAtCaret`): the same popover opens at the caret with the `caretMenu` tasks and\n * `generate` as free prompt; the result is inserted at the caret line (`insertAtCaretLine`).\n * Needs the AiSelectionHighlight extension on the editor.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst MENU_KEY = 'aiSelectionMenu'\nconst { Alert: UAlert, Button: UButton, Icon: UIcon, Input: UInput, Popover: UPopover, Select: USelect } = useUiKit()\nconst notify = useNotifier(() => props.editor)\nconst { t } = useEditorI18n()\nconst { status, output, error, run, abort, reset } = useAiAssistant(() => props.editor)\n\n// Registry order; the free prompt (`custom`) gets its own input below the list.\nconst quickActions = Object.values(AI_TASKS).filter(task => task.selectionMenu && !task.needsPrompt)\nconst custom = AI_TASKS.custom\nconst languageItems = computed(() => AI_LANGUAGES.map(l => ({ label: t(AI_LANGUAGE_KEYS[l]), value: l })))\nconst { settings } = useEditorSettings()\n/** Target language of „Übersetzen“: the settings' default each time the menu opens, changeable per call. */\nconst language = ref<AiLanguage>(settings.value.ai.translateLanguage)\nconst prompt = ref('')\n\n/** 'selection': rewrites the captured selection; 'caret': writes new text at the captured (collapsed) caret. */\nconst mode = ref<'selection' | 'caret'>('selection')\nconst caretActions = Object.values(AI_TASKS).filter(task => task.caretMenu)\nconst actions = computed(() => mode.value === 'caret' ? caretActions : quickActions)\nconst freePrompt = computed(() => mode.value === 'caret' ? AI_TASKS.generate : custom)\n/** Caret mode: whether the document has text before the caret line, for tasks that continue it (`needsSelection`). */\nconst hasTextBefore = ref(false)\nconst promptInput = useTemplateRef<InputExposed>('promptInput')\n\n/** The selection the menu works on, captured when it opened; null while closed. */\nconst range = shallowRef<DocAnchor | null>(null)\n/** The current request; „Erneut versuchen“ repeats it. */\nconst request = shallowRef<{ task: AiTask, prompt: string, language?: AiLanguage } | null>(null)\nconst picking = computed(() => status.value === 'idle' && !output.value)\nconst canApply = computed(() => status.value === 'done' && !!output.value.trim())\nconst requestLabel = computed(() => {\n if (!request.value) return undefined\n const task = t(request.value.task.labelKey)\n return request.value.language ? t('ai.menu.requestWithLanguage', { task, language: t(AI_LANGUAGE_KEYS[request.value.language]) }) : task\n})\n\n// Extra rows other features contribute (`aiMenuActions` of their extensions), offered while the selection qualifies.\nconst menuActions = computed(() => {\n const resolved = range.value && resolveRange(props.editor.state, range.value)\n return resolved ? aiMenuActionsOf(props.editor).filter(action => action.available?.(props.editor, resolved) ?? true) : []\n})\nconst runningAction = ref<string | null>(null)\nlet actionController: AbortController | null = null\nasync function runMenuAction(action: AiMenuAction) {\n const captured = range.value\n const resolved = captured && resolveRange(props.editor.state, captured)\n if (!resolved || runningAction.value) return\n const own = new AbortController()\n actionController = own\n runningAction.value = action.id\n try {\n await action.run(props.editor, resolved, own.signal)\n }\n finally {\n if (actionController === own) actionController = null\n runningAction.value = null\n }\n if (range.value === captured) close(false)\n}\n// Closing the menu cancels a running action; opening it starts from the default translation language.\nwatch(range, (value) => {\n if (!value) {\n actionController?.abort()\n actionController = null\n }\n else language.value = settings.value.ai.translateLanguage\n})\n\nconst shouldShow: NonNullable<BubbleMenuPluginProps['shouldShow']> = ({ editor, element, view, state, from, to }) => {\n if (range.value || !editor.isEditable || state.selection.empty) return false\n if (!view.hasFocus() && !element.contains(document.activeElement)) return false\n if (state.doc.textBetween(from, to, ' ').trim().length === 0) return false\n return abilitiesAtSelection(editor).has(ABILITY.aiRewrite)\n}\n\nfunction openMenu() {\n const { editor } = props\n const selected = selectionRange(editor.state)\n if (selected.from >= selected.to) return\n const captured = captureRange(editor, selected)\n range.value = captured\n showAiSelectionHighlight(editor, captured)\n editor.view.dispatch(editor.state.tr.setMeta(MENU_KEY, 'hide'))\n}\n\n/** Opens the menu at the caret (requested by `openAiMenuAtCaret`, e.g. the `/ai` slash command). */\nfunction openAtCaret() {\n const { editor } = props\n if (range.value || !editor.isEditable) return\n const { $head, head } = editor.state.selection\n mode.value = 'caret'\n hasTextBefore.value = editor.state.doc.textBetween(0, $head.depth ? $head.before() : head, ' ', ' ').trim().length > 0\n range.value = captureRange(editor, { from: head, to: head })\n}\n\nfunction onTransaction({ transaction }: EditorEvents['transaction']) {\n if (transaction.getMeta(AI_MENU_AT_CARET) && hasAbility(props.editor, ABILITY.aiGenerate)) 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) && !range.value && props.editor.isEditable) openMenu()\n}\nprops.editor.on('transaction', onTransaction)\n\n/** Caret mode: the prompt input gets the focus, so the instruction can be typed right away. */\nfunction onOpenAutoFocus(event: Event) {\n if (mode.value !== 'caret') return\n event.preventDefault()\n promptInput.value?.inputRef?.focus()\n}\n\nfunction start(task: AiTask, options: { prompt?: string, language?: AiLanguage } = {}) {\n request.value = { task, prompt: options.prompt ?? '', language: options.language }\n generate()\n}\n\n/** Runs the current request on the current text of the captured range, with the current document as context. */\nfunction generate() {\n const { editor } = props\n const current = request.value\n if (!range.value || !current) return\n if (mode.value === 'caret') {\n generateAtCaret(current)\n return\n }\n const resolved = resolveRange(editor.state, range.value)\n if (!resolved || resolved.from >= resolved.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 void run({\n task: current.task.id,\n prompt: current.prompt,\n language: current.language,\n selection: rangeMarkdown(editor, resolved),\n context: documentMarkdown(editor),\n blocks,\n })\n}\n\n/**\n * Caret mode: tasks with `needsSelection` (continue) get the document up to the caret line as `selection` and the rest\n * as `context`; the others the whole document as context.\n */\nfunction generateAtCaret(current: NonNullable<typeof request.value>) {\n const { editor } = props\n const at = range.value && resolveRange(editor.state, range.value)\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 = current.task.needsSelection\n const { blocks } = aiContextOf(editor)\n void run({\n task: current.task.id,\n prompt: current.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}\n\n/** 'replace' opens the in-document review of the result (AiReviewBar); nothing is written until accepted there. */\nfunction apply(action: AiResultAction) {\n if (!range.value || !canApply.value) return\n const outcome = applyAiResult(props.editor, action, range.value, output.value)\n if (outcome !== 'ok') {\n notify(aiApplyFailure(outcome, t))\n if (outcome !== 'unchanged') return\n }\n close(false)\n}\n\nfunction insertAtCaret() {\n if (!range.value || !canApply.value) return\n if (!insertAtCaretLine(props.editor, range.value, output.value)) {\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/** Closes the menu and removes the highlight; `restoreSelection` selects the captured range (caret mode: caret) again. */\nfunction close(restoreSelection: boolean) {\n const { editor } = props\n const captured = range.value\n const caret = mode.value === 'caret'\n reset()\n request.value = null\n prompt.value = ''\n range.value = null\n mode.value = 'selection'\n if (editor.isDestroyed) return\n hideAiSelectionHighlight(editor)\n const resolved = captured && restoreSelection ? resolveRange(editor.state, captured) : null\n if (captured) releaseRanges(editor, [captured])\n if (resolved && (caret || resolved.from < resolved.to)) editor.chain().focus().setTextSelection(resolved).run()\n}\n\n// Popover anchor: the highlighted range (its plugin state follows local and remote edits); in caret mode the caret.\nlet anchorRect = new DOMRect()\nconst reference = {\n getBoundingClientRect(): DOMRect {\n const { editor } = props\n if (editor.isDestroyed) return anchorRect\n if (mode.value === 'caret') return caretRect(editor)\n const highlight = aiSelectionHighlightKey.getState(editor.state)\n const highlighted = highlight && resolveRange(editor.state, highlight.anchor)\n if (!highlighted || highlighted.from >= highlighted.to) return anchorRect\n const { view } = editor\n const start = view.coordsAtPos(highlighted.from, 1)\n const end = view.coordsAtPos(highlighted.to, -1)\n // Across lines the range spans the editor's width.\n const bounds = view.dom.getBoundingClientRect()\n const oneLine = end.top < start.bottom\n const left = oneLine ? start.left : bounds.left\n const right = oneLine ? end.right : bounds.right\n anchorRect = new DOMRect(left, start.top, Math.max(right - left, 1), end.bottom - start.top)\n return anchorRect\n },\n get contextElement() {\n return props.editor.view.dom\n },\n}\n\n// The caret's position is resolved from its anchor once per editor state (the anchor is measured every frame).\nlet caretAt: { state: EditorState, pos: number | null } | null = null\nfunction caretRect(editor: Editor): DOMRect {\n if (caretAt?.state !== editor.state) {\n caretAt = { state: editor.state, pos: range.value ? resolveRange(editor.state, range.value)?.from ?? null : null }\n }\n if (caretAt.pos === null) return anchorRect\n const coords = editor.view.coordsAtPos(caretAt.pos)\n anchorRect = new DOMRect(coords.left, coords.top, 1, coords.bottom - coords.top)\n return anchorRect\n}\n\nconst preview = useEditor({\n editable: false,\n extensions: previewExtensions(props.editor),\n editorProps: { attributes: { class: 'tiptap ai-selection-preview' } },\n})\n\n// Coalesce streamed deltas: re-parse the Markdown at most every 80 ms; speaker lines show as segments, as applied.\nlet previewTimer: ReturnType<typeof setTimeout> | undefined\nwatch(output, () => {\n if (previewTimer) return\n previewTimer = setTimeout(() => {\n previewTimer = undefined\n const instance = preview.value\n if (!instance) return\n instance.commands.setContent({ type: 'doc', content: parseBlocks(props.editor, output.value).blocks }, { emitUpdate: false })\n }, 80)\n})\n\nonBeforeUnmount(() => {\n props.editor.off('transaction', onTransaction)\n clearTimeout(previewTimer)\n abort()\n actionController?.abort()\n if (!props.editor.isDestroyed) hideAiSelectionHighlight(props.editor)\n preview.value?.destroy()\n})\n</script>\n\n<template>\n <BubbleMenu :editor=\"editor\" :plugin-key=\"MENU_KEY\" :should-show=\"shouldShow\" :options=\"{ placement: 'top', offset: 8 }\" class=\"z-10\">\n <div class=\"flex rounded-md border border-[var(--sk-border)] bg-[var(--sk-bg)] p-0.5 shadow-md\">\n <UButton size=\"sm\" color=\"neutral\" variant=\"ghost\" icon=\"i-lucide-sparkles\" :label=\"t('ai.menu.trigger')\" @click=\"openMenu\" />\n </div>\n </BubbleMenu>\n\n <UPopover\n :open=\"range !== null\"\n :reference=\"reference\"\n :dismissible=\"picking\"\n :content=\"{ side: 'bottom', align: 'start', updatePositionStrategy: 'always', onOpenAutoFocus }\"\n :ui=\"{ content: 'w-[30rem] max-w-[calc(100vw-2rem)]' }\"\n @update:open=\"(value: boolean) => { if (!value) close(mode === 'caret') }\"\n >\n <template #content>\n <div v-if=\"picking\" class=\"flex flex-col gap-1 p-2\">\n <!-- Caret mode generates blocks, selection mode rewrites text in the block: each has its own model slot. -->\n <div class=\"flex items-center justify-end gap-2 px-1 pb-1\">\n <span class=\"text-xs text-[var(--sk-muted)]\">{{ t('ai.menu.model') }}</span>\n <ModelQuickSwitch :slot=\"mode === 'caret' ? 'generate' : 'rewrite'\" size=\"xs\" class=\"w-56\" />\n </div>\n <div v-for=\"task in actions\" :key=\"task.id\" class=\"flex items-center gap-1\">\n <UButton\n :icon=\"task.icon\"\n :label=\"t(task.labelKey)\"\n color=\"neutral\"\n variant=\"ghost\"\n class=\"flex-1 justify-start\"\n :disabled=\"mode === 'caret' && task.needsSelection && !hasTextBefore\"\n @click=\"start(task, task.needsLanguage ? { language } : {})\"\n />\n <USelect v-if=\"task.needsLanguage\" v-model=\"language\" :items=\"languageItems\" size=\"sm\" class=\"w-36\" :aria-label=\"t('ai.menu.targetLanguage')\" />\n </div>\n <UButton\n v-for=\"action in menuActions\"\n :key=\"action.id\"\n :icon=\"action.icon\"\n :label=\"action.label\"\n color=\"neutral\"\n variant=\"ghost\"\n class=\"justify-start\"\n :loading=\"runningAction === action.id\"\n :disabled=\"runningAction !== null && runningAction !== action.id\"\n @click=\"runMenuAction(action)\"\n />\n <form class=\"mt-1 flex gap-1 border-t border-[var(--sk-border)] pt-2\" @submit.prevent=\"prompt.trim() && start(freePrompt, { prompt })\">\n <UInput ref=\"promptInput\" v-model=\"prompt\" :icon=\"freePrompt.icon\" :placeholder=\"freePrompt.promptLabelKey && t(freePrompt.promptLabelKey)\" class=\"flex-1\" />\n <UButton type=\"submit\" icon=\"i-lucide-arrow-up\" :disabled=\"!prompt.trim()\" :aria-label=\"t('ai.menu.sendInstruction')\" />\n </form>\n </div>\n\n <div v-else class=\"flex flex-col gap-3 p-3\">\n <div class=\"flex items-center gap-2 text-sm font-medium\">\n <UIcon v-if=\"request\" :name=\"request.task.icon\" class=\"size-4\" />\n <span class=\"flex-1 truncate\">{{ requestLabel }}</span>\n <UIcon v-if=\"status === 'streaming'\" name=\"i-lucide-loader-circle\" class=\"size-4 animate-spin text-[var(--sk-muted)]\" />\n </div>\n <UAlert v-if=\"error\" color=\"error\" variant=\"subtle\" :title=\"error\" />\n <div class=\"max-h-72 min-h-16 overflow-auto rounded-md border border-[var(--sk-border)] p-3 text-sm\">\n <EditorContent v-if=\"preview\" :editor=\"preview\" />\n </div>\n <div class=\"flex flex-wrap justify-end gap-2\">\n <UButton v-if=\"status === 'streaming'\" size=\"sm\" color=\"neutral\" variant=\"ghost\" :label=\"t('ai.menu.cancel')\" @click=\"abort()\" />\n <UButton size=\"sm\" color=\"neutral\" variant=\"ghost\" :label=\"t('ai.menu.discard')\" @click=\"close(true)\" />\n <UButton\n size=\"sm\"\n color=\"neutral\"\n variant=\"outline\"\n icon=\"i-lucide-rotate-ccw\"\n :label=\"t('ai.menu.retry')\"\n :disabled=\"status === 'streaming'\"\n @click=\"generate\"\n />\n <UButton v-if=\"mode === 'caret'\" size=\"sm\" icon=\"i-lucide-text-cursor-input\" :label=\"t('ai.menu.insert')\" :disabled=\"!canApply\" @click=\"insertAtCaret\" />\n <template v-else>\n <UButton\n size=\"sm\"\n color=\"neutral\"\n variant=\"outline\"\n icon=\"i-lucide-list-plus\"\n :label=\"t('ai.menu.insertBelow')\"\n :disabled=\"!canApply\"\n @click=\"apply('insert-below')\"\n />\n <UButton size=\"sm\" icon=\"i-lucide-replace\" :label=\"t('ai.menu.replace')\" :disabled=\"!canApply\" @click=\"apply('replace')\" />\n </template>\n </div>\n </div>\n </template>\n </UPopover>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,WAAW;;;;;EAFjB,MAAM,QAAQ;EAGd,MAAM,EAAE,OAAO,QAAQ,QAAQ,SAAS,MAAM,OAAO,OAAO,QAAQ,SAAS,UAAU,QAAQ,YAAY,SAAS;EACpH,MAAM,SAAS,kBAAkB,MAAM,MAAM;EAC7C,MAAM,EAAE,MAAM,cAAc;EAC5B,MAAM,EAAE,QAAQ,QAAQ,OAAO,KAAK,OAAO,UAAU,qBAAqB,MAAM,MAAM;EAGtF,MAAM,eAAe,OAAO,OAAO,QAAQ,CAAC,CAAC,QAAO,SAAQ,KAAK,iBAAiB,CAAC,KAAK,WAAW;EACnG,MAAM,SAAS,SAAS;EACxB,MAAM,gBAAgB,eAAe,aAAa,KAAI,OAAM;GAAE,OAAO,EAAE,iBAAiB,EAAE;GAAG,OAAO;EAAE,EAAE,CAAC;EACzG,MAAM,EAAE,aAAa,kBAAkB;;EAEvC,MAAM,WAAW,IAAgB,SAAS,MAAM,GAAG,iBAAiB;EACpE,MAAM,SAAS,IAAI,EAAE;;EAGrB,MAAM,OAAO,IAA2B,WAAW;EACnD,MAAM,eAAe,OAAO,OAAO,QAAQ,CAAC,CAAC,QAAO,SAAQ,KAAK,SAAS;EAC1E,MAAM,UAAU,eAAe,KAAK,UAAU,UAAU,eAAe,YAAY;EACnF,MAAM,aAAa,eAAe,KAAK,UAAU,UAAU,SAAS,WAAW,MAAM;;EAErF,MAAM,gBAAgB,IAAI,KAAK;EAC/B,MAAM,cAAc,eAA6B,aAAa;;EAG9D,MAAM,QAAQ,WAA6B,IAAI;;EAE/C,MAAM,UAAU,WAA2E,IAAI;EAC/F,MAAM,UAAU,eAAe,OAAO,UAAU,UAAU,CAAC,OAAO,KAAK;EACvE,MAAM,WAAW,eAAe,OAAO,UAAU,UAAU,CAAC,CAAC,OAAO,MAAM,KAAK,CAAC;EAChF,MAAM,eAAe,eAAe;GAClC,IAAI,CAAC,QAAQ,OAAO,OAAO,KAAA;GAC3B,MAAM,OAAO,EAAE,QAAQ,MAAM,KAAK,QAAQ;GAC1C,OAAO,QAAQ,MAAM,WAAW,EAAE,+BAA+B;IAAE;IAAM,UAAU,EAAE,iBAAiB,QAAQ,MAAM,SAAS;GAAE,CAAC,IAAI;EACtI,CAAC;EAGD,MAAM,cAAc,eAAe;GACjC,MAAM,WAAW,MAAM,SAAS,aAAa,MAAM,OAAO,OAAO,MAAM,KAAK;GAC5E,OAAO,WAAW,gBAAgB,MAAM,MAAM,CAAC,CAAC,QAAO,WAAU,OAAO,YAAY,MAAM,QAAQ,QAAQ,KAAK,IAAI,IAAI,CAAC;EAC1H,CAAC;EACD,MAAM,gBAAgB,IAAmB,IAAI;EAC7C,IAAI,mBAA2C;EAC/C,eAAe,cAAc,QAAsB;GACjD,MAAM,WAAW,MAAM;GACvB,MAAM,WAAW,YAAY,aAAa,MAAM,OAAO,OAAO,QAAQ;GACtE,IAAI,CAAC,YAAY,cAAc,OAAO;GACtC,MAAM,MAAM,IAAI,gBAAgB;GAChC,mBAAmB;GACnB,cAAc,QAAQ,OAAO;GAC7B,IAAI;IACF,MAAM,OAAO,IAAI,MAAM,QAAQ,UAAU,IAAI,MAAM;GACrD,UACQ;IACN,IAAI,qBAAqB,KAAK,mBAAmB;IACjD,cAAc,QAAQ;GACxB;GACA,IAAI,MAAM,UAAU,UAAU,MAAM,KAAK;EAC3C;EAEA,MAAM,QAAQ,UAAU;GACtB,IAAI,CAAC,OAAO;IACV,kBAAkB,MAAM;IACxB,mBAAmB;GACrB,OACK,SAAS,QAAQ,SAAS,MAAM,GAAG;EAC1C,CAAC;EAED,MAAM,cAAgE,EAAE,QAAQ,SAAS,MAAM,OAAO,MAAM,SAAS;GACnH,IAAI,MAAM,SAAS,CAAC,OAAO,cAAc,MAAM,UAAU,OAAO,OAAO;GACvE,IAAI,CAAC,KAAK,SAAS,KAAK,CAAC,QAAQ,SAAS,SAAS,aAAa,GAAG,OAAO;GAC1E,IAAI,MAAM,IAAI,YAAY,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;GACrE,OAAO,qBAAqB,MAAM,CAAC,CAAC,IAAI,QAAQ,SAAS;EAC3D;EAEA,SAAS,WAAW;GAClB,MAAM,EAAE,WAAW;GACnB,MAAM,WAAW,eAAe,OAAO,KAAK;GAC5C,IAAI,SAAS,QAAQ,SAAS,IAAI;GAClC,MAAM,WAAW,aAAa,QAAQ,QAAQ;GAC9C,MAAM,QAAQ;GACd,yBAAyB,QAAQ,QAAQ;GACzC,OAAO,KAAK,SAAS,OAAO,MAAM,GAAG,QAAQ,UAAU,MAAM,CAAC;EAChE;;EAGA,SAAS,cAAc;GACrB,MAAM,EAAE,WAAW;GACnB,IAAI,MAAM,SAAS,CAAC,OAAO,YAAY;GACvC,MAAM,EAAE,OAAO,SAAS,OAAO,MAAM;GACrC,KAAK,QAAQ;GACb,cAAc,QAAQ,OAAO,MAAM,IAAI,YAAY,GAAG,MAAM,QAAQ,MAAM,OAAO,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS;GACrH,MAAM,QAAQ,aAAa,QAAQ;IAAE,MAAM;IAAM,IAAI;GAAK,CAAC;EAC7D;EAEA,SAAS,cAAc,EAAE,eAA4C;GACnE,IAAI,YAAY,QAAA,eAAwB,KAAK,WAAW,MAAM,QAAQ,QAAQ,UAAU,GAAG,YAAY;GAEvG,IAAI,YAAY,QAAA,oBAA6B,KAAK,CAAC,MAAM,SAAS,MAAM,OAAO,YAAY,SAAS;EACtG;EACA,MAAM,OAAO,GAAG,eAAe,aAAa;;EAG5C,SAAS,gBAAgB,OAAc;GACrC,IAAI,KAAK,UAAU,SAAS;GAC5B,MAAM,eAAe;GACrB,YAAY,OAAO,UAAU,MAAM;EACrC;EAEA,SAAS,MAAM,MAAc,UAAsD,CAAC,GAAG;GACrF,QAAQ,QAAQ;IAAE;IAAM,QAAQ,QAAQ,UAAU;IAAI,UAAU,QAAQ;GAAS;GACjF,SAAS;EACX;;EAGA,SAAS,WAAW;GAClB,MAAM,EAAE,WAAW;GACnB,MAAM,UAAU,QAAQ;GACxB,IAAI,CAAC,MAAM,SAAS,CAAC,SAAS;GAC9B,IAAI,KAAK,UAAU,SAAS;IAC1B,gBAAgB,OAAO;IACvB;GACF;GACA,MAAM,WAAW,aAAa,OAAO,OAAO,MAAM,KAAK;GACvD,IAAI,CAAC,YAAY,SAAS,QAAQ,SAAS,IAAI;IAC7C,OAAO;KAAE,OAAO,EAAE,wBAAwB;KAAG,aAAa,EAAE,2BAA2B;KAAG,MAAM;IAAQ,CAAC;IACzG;GACF;GACA,MAAM,EAAE,WAAW,YAAY,MAAM;GACrC,IAAS;IACP,MAAM,QAAQ,KAAK;IACnB,QAAQ,QAAQ;IAChB,UAAU,QAAQ;IAClB,WAAW,cAAc,QAAQ,QAAQ;IACzC,SAAS,iBAAiB,MAAM;IAChC;GACF,CAAC;EACH;;;;;EAMA,SAAS,gBAAgB,SAA4C;GACnE,MAAM,EAAE,WAAW;GACnB,MAAM,KAAK,MAAM,SAAS,aAAa,OAAO,OAAO,MAAM,KAAK;GAChE,IAAI,CAAC,IAAI;IACP,OAAO;KAAE,OAAO,EAAE,wBAAwB;KAAG,aAAa,EAAE,sBAAsB;KAAG,MAAM;IAAQ,CAAC;IACpG;GACF;GACA,MAAM,EAAE,QAAQ,OAAO;GACvB,MAAM,MAAM,IAAI,QAAQ,GAAG,IAAI;GAC/B,MAAM,cAAc,IAAI,QAAQ,IAAI,OAAO,IAAI,GAAG;GAClD,MAAM,aAAa,IAAI,QAAQ,IAAI,MAAM,IAAI,GAAG;GAChD,MAAM,YAAY,QAAQ,KAAK;GAC/B,MAAM,EAAE,WAAW,YAAY,MAAM;GACrC,IAAS;IACP,MAAM,QAAQ,KAAK;IACnB,QAAQ,QAAQ;IAChB,WAAW,YAAY,cAAc,QAAQ;KAAE,MAAM;KAAG,IAAI;IAAY,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,SAAS,IAAI;IACzG,SAAS,YAAY,cAAc,QAAQ;KAAE,MAAM;KAAY,IAAI,IAAI,QAAQ;IAAK,CAAC,CAAC,CAAC,MAAM,GAAG,UAAU,OAAO,IAAI,iBAAiB,MAAM;IAC5I;GACF,CAAC;EACH;;EAGA,SAAS,MAAM,QAAwB;GACrC,IAAI,CAAC,MAAM,SAAS,CAAC,SAAS,OAAO;GACrC,MAAM,UAAU,cAAc,MAAM,QAAQ,QAAQ,MAAM,OAAO,OAAO,KAAK;GAC7E,IAAI,YAAY,MAAM;IACpB,OAAO,eAAe,SAAS,CAAC,CAAC;IACjC,IAAI,YAAY,aAAa;GAC/B;GACA,MAAM,KAAK;EACb;EAEA,SAAS,gBAAgB;GACvB,IAAI,CAAC,MAAM,SAAS,CAAC,SAAS,OAAO;GACrC,IAAI,CAAC,kBAAkB,MAAM,QAAQ,MAAM,OAAO,OAAO,KAAK,GAAG;IAC/D,OAAO;KAAE,OAAO,EAAE,wBAAwB;KAAG,aAAa,EAAE,wBAAwB;KAAG,MAAM;IAAQ,CAAC;IACtG;GACF;GACA,MAAM,KAAK;EACb;;EAGA,SAAS,MAAM,kBAA2B;GACxC,MAAM,EAAE,WAAW;GACnB,MAAM,WAAW,MAAM;GACvB,MAAM,QAAQ,KAAK,UAAU;GAC7B,MAAM;GACN,QAAQ,QAAQ;GAChB,OAAO,QAAQ;GACf,MAAM,QAAQ;GACd,KAAK,QAAQ;GACb,IAAI,OAAO,aAAa;GACxB,yBAAyB,MAAM;GAC/B,MAAM,WAAW,YAAY,mBAAmB,aAAa,OAAO,OAAO,QAAQ,IAAI;GACvF,IAAI,UAAU,cAAc,QAAQ,CAAC,QAAQ,CAAC;GAC9C,IAAI,aAAa,SAAS,SAAS,OAAO,SAAS,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,iBAAiB,QAAQ,CAAC,CAAC,IAAI;EAChH;EAGA,IAAI,aAAa,IAAI,QAAQ;EAC7B,MAAM,YAAY;GAChB,wBAAiC;IAC/B,MAAM,EAAE,WAAW;IACnB,IAAI,OAAO,aAAa,OAAO;IAC/B,IAAI,KAAK,UAAU,SAAS,OAAO,UAAU,MAAM;IACnD,MAAM,YAAY,wBAAwB,SAAS,OAAO,KAAK;IAC/D,MAAM,cAAc,aAAa,aAAa,OAAO,OAAO,UAAU,MAAM;IAC5E,IAAI,CAAC,eAAe,YAAY,QAAQ,YAAY,IAAI,OAAO;IAC/D,MAAM,EAAE,SAAS;IACjB,MAAM,QAAQ,KAAK,YAAY,YAAY,MAAM,CAAC;IAClD,MAAM,MAAM,KAAK,YAAY,YAAY,IAAI,EAAE;IAE/C,MAAM,SAAS,KAAK,IAAI,sBAAsB;IAC9C,MAAM,UAAU,IAAI,MAAM,MAAM;IAChC,MAAM,OAAO,UAAU,MAAM,OAAO,OAAO;IAC3C,MAAM,QAAQ,UAAU,IAAI,QAAQ,OAAO;IAC3C,aAAa,IAAI,QAAQ,MAAM,MAAM,KAAK,KAAK,IAAI,QAAQ,MAAM,CAAC,GAAG,IAAI,SAAS,MAAM,GAAG;IAC3F,OAAO;GACT;GACA,IAAI,iBAAiB;IACnB,OAAO,MAAM,OAAO,KAAK;GAC3B;EACF;EAGA,IAAI,UAA6D;EACjE,SAAS,UAAU,QAAyB;GAC1C,IAAI,SAAS,UAAU,OAAO,OAC5B,UAAU;IAAE,OAAO,OAAO;IAAO,KAAK,MAAM,QAAQ,aAAa,OAAO,OAAO,MAAM,KAAK,CAAC,EAAE,QAAQ,OAAO;GAAK;GAEnH,IAAI,QAAQ,QAAQ,MAAM,OAAO;GACjC,MAAM,SAAS,OAAO,KAAK,YAAY,QAAQ,GAAG;GAClD,aAAa,IAAI,QAAQ,OAAO,MAAM,OAAO,KAAK,GAAG,OAAO,SAAS,OAAO,GAAG;GAC/E,OAAO;EACT;EAEA,MAAM,UAAU,UAAU;GACxB,UAAU;GACV,YAAY,kBAAkB,MAAM,MAAM;GAC1C,aAAa,EAAE,YAAY,EAAE,OAAO,8BAA8B,EAAE;EACtE,CAAC;EAGD,IAAI;EACJ,MAAM,cAAc;GAClB,IAAI,cAAc;GAClB,eAAe,iBAAiB;IAC9B,eAAe,KAAA;IACf,MAAM,WAAW,QAAQ;IACzB,IAAI,CAAC,UAAU;IACf,SAAS,SAAS,WAAW;KAAE,MAAM;KAAO,SAAS,YAAY,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC;IAAO,GAAG,EAAE,YAAY,MAAM,CAAC;GAC9H,GAAG,EAAE;EACP,CAAC;EAED,sBAAsB;GACpB,MAAM,OAAO,IAAI,eAAe,aAAa;GAC7C,aAAa,YAAY;GACzB,MAAM;GACN,kBAAkB,MAAM;GACxB,IAAI,CAAC,MAAM,OAAO,aAAa,yBAAyB,MAAM,MAAM;GACpE,QAAQ,OAAO,QAAQ;EACzB,CAAC;;GAIC,OAAA,UAAA,GAAA,mBAAA,UAAA,MAAA,CAAAG,YAIaC,MAAA,UAAA,GAAA;IAJA,QAAQ,QAAA;IAAS,cAAY;IAAW,eAAa;IAAa,SAAS;KAAA,WAAA;KAAA,QAAA;IAAA;IAAiC,OAAM;;IAC7H,SAAAC,cAEM,CAFNC,mBAEM,OAFN,YAEM,CADJH,YAA8HC,MAAA,OAAA,GAAA;KAArH,MAAK;KAAK,OAAM;KAAU,SAAQ;KAAQ,MAAK;KAAqB,OAAOA,MAAA,CAAA,CAAC,CAAA,iBAAA;KAAsB,SAAO;;;GAItH,GAAA,GAAA,CAAA,QAAA,CAAA,GAAAD,YAmFWC,MAAA,QAAA,GAAA;IAlFR,MAAM,MAAA,UAAK;IACA;IACX,aAAa,QAAA;IACb,SAAO;KAAA,MAAA;KAAA,OAAA;KAAA,wBAAA;KAAsE;IAAe;IAC5F,IAAI,EAAA,SAAA,qCAAA;IACJ,iBAAW,OAAA,OAAA,OAAA,MAAG,UAAc;KAAA,IAAA,CAAY,OAAO,MAAM,KAAA,UAAI,OAAA;IAAA;;IAE/C,SAAOC,cAmCV,CAlCK,QAAA,SAAXL,UAAA,GAAAC,mBAkCM,OAlCN,YAkCM;KAjCJM,mBAAA,wGAAA;KACAD,mBAGM,OAHN,YAGM,CAFJA,mBAA4E,QAA5E,YAA4EE,gBAA5BJ,MAAA,CAAA,CAAC,CAAA,eAAA,CAAA,GAAA,CAAA,GACjDD,YAA6FM,aAAA;MAA1E,MAAM,KAAA,UAAI,UAAA,aAAA;MAAuC,MAAK;MAAK,OAAM;;MAEtFT,UAAA,IAAA,GAAAC,mBAWMC,UAAA,MAAAQ,WAXc,QAAA,QAAR,SAAI;MAAhB,OAAAV,UAAA,GAAAC,mBAWM,OAAA;OAXwB,KAAK,KAAK;OAAI,OAAM;MAChD,GAAA,CAAAE,YAQEC,MAAA,OAAA,GAAA;OAPC,MAAM,KAAK;OACX,OAAOA,MAAA,CAAA,CAAC,CAAC,KAAK,QAAQ;OACvB,OAAM;OACN,SAAQ;OACR,OAAM;OACL,UAAU,KAAA,UAAI,WAAgB,KAAK,kBAAc,CAAK,cAAA;OACtD,UAAK,WAAE,MAAM,MAAM,KAAK,gBAAa,EAAA,UAAK,SAAA,MAAQ,IAAA,CAAA,CAAA;;;;;;MAEtC,CAAA,GAAA,KAAK,iBAApBJ,UAAA,GAAAW,YAAgJP,MAAA,OAAA,GAAA;;OAApG,YAAA,SAAA;OAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,SAAQ,QAAA;OAAG,OAAO,cAAA;OAAe,MAAK;OAAK,OAAM;OAAQ,cAAYA,MAAA,CAAA,CAAC,CAAA,wBAAA;;;;;;;MAEpHJ,UAAA,IAAA,GAAAC,mBAWEC,UAAA,MAAAQ,WAViB,YAAA,QAAV,WAAM;MADf,OAAAV,UAAA,GAAAW,YAWEP,MAAA,OAAA,GAAA;OATC,KAAK,OAAO;OACZ,MAAM,OAAO;OACb,OAAO,OAAO;OACf,OAAM;OACN,SAAQ;OACR,OAAM;OACL,SAAS,cAAA,UAAkB,OAAO;OAClC,UAAU,cAAA,UAAa,QAAa,cAAA,UAAkB,OAAO;OAC7D,UAAK,WAAE,cAAc,MAAM;;;;;;;;;KAE9BE,mBAGO,QAAA;MAHD,OAAM;MAA2D,UAAM,OAAA,OAAA,OAAA,KAAAM,eAAA,WAAU,OAAA,MAAO,KAAI,KAAM,MAAM,WAAA,OAAU,EAAA,QAAI,OAAA,MAAM,CAAA,GAAA,CAAA,SAAA,CAAA;KAChI,GAAA,CAAAT,YAA6JC,MAAA,MAAA,GAAA;MAAjJ,SAAA;MAAJ,KAAI;MAAuB,YAAA,OAAA;MAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,OAAM,QAAA;MAAG,MAAM,WAAA,MAAW;MAAO,aAAa,WAAA,MAAW,kBAAkBA,MAAA,CAAA,CAAC,CAAC,WAAA,MAAW,cAAc;MAAG,OAAM;;;;;KAClJ,CAAA,GAAAD,YAAwHC,MAAA,OAAA,GAAA;MAA/G,MAAK;MAAS,MAAK;MAAqB,UAAQ,CAAG,OAAA,MAAO,KAAI;MAAK,cAAYA,MAAA,CAAA,CAAC,CAAA,yBAAA;;IAI7F,CAAA,MAAAJ,UAAA,GAAAC,mBAoCM,OApCN,YAoCM;KAnCJK,mBAIM,OAJN,YAIM;MAHS,QAAA,SAAbN,UAAA,GAAAW,YAAiEP,MAAA,KAAA,GAAA;;OAA1C,MAAM,QAAA,MAAQ,KAAK;OAAM,OAAM;;MACtDE,mBAAuD,QAAvD,YAAuDE,gBAAtB,aAAA,KAAY,GAAA,CAAA;MAChCJ,MAAA,MAAA,MAAM,eAAnBJ,UAAA,GAAAW,YAAwHP,MAAA,KAAA,GAAA;;OAAnF,MAAK;OAAyB,OAAM;;;KAE7DA,MAAA,KAAA,KAAdJ,UAAA,GAAAW,YAAqEP,MAAA,MAAA,GAAA;;MAAhD,OAAM;MAAQ,SAAQ;MAAU,OAAOA,MAAA,KAAA;;KAC5DE,mBAEM,OAFN,YAEM,CADiBF,MAAA,OAAA,KAArBJ,UAAA,GAAAW,YAAkDP,MAAA,aAAA,GAAA;;MAAnB,QAAQA,MAAA,OAAA;;KAEzCE,mBAyBM,OAzBN,YAyBM;MAxBWF,MAAA,MAAA,MAAM,eAArBJ,UAAA,GAAAW,YAAiIP,MAAA,OAAA,GAAA;;OAA1F,MAAK;OAAK,OAAM;OAAU,SAAQ;OAAS,OAAOA,MAAA,CAAA,CAAC,CAAA,gBAAA;OAAqB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAEA,MAAA,KAAA,CAAK,CAAA;;MAC3HD,YAAwGC,MAAA,OAAA,GAAA;OAA/F,MAAK;OAAK,OAAM;OAAU,SAAQ;OAAS,OAAOA,MAAA,CAAA,CAAC,CAAA,iBAAA;OAAsB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAK,IAAA;;MAC9FD,YAQEC,MAAA,OAAA,GAAA;OAPA,MAAK;OACL,OAAM;OACN,SAAQ;OACR,MAAK;OACJ,OAAOA,MAAA,CAAA,CAAC,CAAA,eAAA;OACR,UAAUA,MAAA,MAAA,MAAM;OAChB,SAAO;;MAEK,KAAA,UAAI,WAAnBJ,UAAA,GAAAW,YAAyJP,MAAA,OAAA,GAAA;;OAAxH,MAAK;OAAK,MAAK;OAA8B,OAAOA,MAAA,CAAA,CAAC,CAAA,gBAAA;OAAqB,UAAQ,CAAG,SAAA;OAAW,SAAO;MACxI,GAAA,MAAA,GAAA,CAAA,SAAA,UAAA,CAAA,MAAAJ,UAAA,GAAAC,mBAWWC,UAAA,EAAA,KAAA,EAAA,GAAA,CAVTC,YAQEC,MAAA,OAAA,GAAA;OAPA,MAAK;OACL,OAAM;OACN,SAAQ;OACR,MAAK;OACJ,OAAOA,MAAA,CAAA,CAAC,CAAA,qBAAA;OACR,UAAQ,CAAG,SAAA;OACX,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAK,cAAA;MAEf,GAAA,MAAA,GAAA,CAAA,SAAA,UAAA,CAAA,GAAAD,YAA2HC,MAAA,OAAA,GAAA;OAAlH,MAAK;OAAK,MAAK;OAAoB,OAAOA,MAAA,CAAA,CAAC,CAAA,iBAAA;OAAsB,UAAQ,CAAG,SAAA;OAAW,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAK,SAAA"}
|
|
1
|
+
{"version":3,"file":"AiSelectionMenu.js","names":["_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_withCtx","_createElementVNode","_createCommentVNode","_toDisplayString","ModelQuickSwitch","_renderList","_createBlock","_withModifiers"],"sources":["../../../../src/features/ai/components/AiSelectionMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'\nimport type { EditorState } from '@tiptap/pm/state'\nimport { type Editor, EditorContent, useEditor } from '@tiptap/vue-3'\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\nimport { computed, onBeforeUnmount, useTemplateRef, watch } from 'vue'\nimport { parseBlocks, resolveRange } from '../../../core'\nimport { useEditorSettings } from '../../../vue/settings'\nimport type { InputExposed } from '../../../vue/kit/types'\nimport { useUiKit } from '../../../vue/kit/use-ui-kit'\nimport { useStoreRef } from '../../../vue/store-ref'\nimport { useNotifier } from '../../../vue/use-notifier'\nimport { useEditorI18n } from '../../../vue/use-editor-i18n'\nimport { AI_LANGUAGES } from '../../../settings'\nimport { aiSelectionHighlightKey } from '../ai-selection-highlight'\nimport { previewExtensions } from '../preview-extensions'\nimport { AI_LANGUAGE_KEYS } from '../protocol'\nimport { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from '../selection-flow'\nimport { useAiAssistant } from '../use-ai-assistant'\nimport ModelQuickSwitch from './ModelQuickSwitch.vue'\n\n/**\n * AI quick actions on a text selection: a bubble menu entry opens a popover with the selection tasks of the registry\n * (`selectionMenu`), the result streams into a preview and is applied over the range captured when the menu opened.\n * Caret mode (`/ai`, see `openAiMenuAtCaret`): the same popover opens at the caret with the `caretMenu` tasks and\n * `generate` as free prompt; the result is inserted at the caret line (`insertAtCaretLine`).\n * Needs the AiSelectionHighlight extension on the editor. The flow (`createAiSelectionFlow`) owns every step; this\n * component renders its state, places the popover and shows the preview.\n */\nconst props = defineProps<{ editor: Editor }>()\n\nconst { Alert: UAlert, Button: UButton, Icon: UIcon, Input: UInput, Popover: UPopover, Select: USelect } = useUiKit()\nconst notify = useNotifier(() => props.editor)\nconst { t } = useEditorI18n()\nconst { model: assistant } = useAiAssistant(() => props.editor)\nconst { settings } = useEditorSettings()\nconst flow = createAiSelectionFlow({ assistant, notify, t, editor: () => props.editor, language: () => settings.value.ai.translateLanguage })\nconst state = useStoreRef(flow.state)\n\nconst languageItems = computed(() => AI_LANGUAGES.map(l => ({ label: t(AI_LANGUAGE_KEYS[l]), value: l })))\nconst prompt = computed({ get: () => state.value.prompt, set: flow.setPrompt })\nconst language = computed({ get: () => state.value.language, set: flow.setLanguage })\nconst promptInput = useTemplateRef<InputExposed>('promptInput')\nconst requestLabel = computed(() => {\n const request = state.value.request\n if (!request) return undefined\n const task = t(request.task.labelKey)\n return request.language ? t('ai.menu.requestWithLanguage', { task, language: t(AI_LANGUAGE_KEYS[request.language]) }) : task\n})\n// Extra rows other features contribute (`aiMenuActions` of their extensions), offered while the selection qualifies.\nconst menuActions = computed(() => state.value.open ? flow.menuActions() : [])\n\nconst shouldShow: NonNullable<BubbleMenuPluginProps['shouldShow']> = ({ element, view }) =>\n (view.hasFocus() || element.contains(document.activeElement)) && flow.offersSelection()\n\n/** Caret mode: the prompt input gets the focus, so the instruction can be typed right away. */\nfunction onOpenAutoFocus(event: Event) {\n if (state.value.mode !== 'caret') return\n event.preventDefault()\n promptInput.value?.inputRef?.focus()\n}\n\n\n// Popover anchor: the highlighted range (its plugin state follows local and remote edits); in caret mode the caret.\nlet anchorRect = new DOMRect()\nconst reference = {\n getBoundingClientRect(): DOMRect {\n const { editor } = props\n if (editor.isDestroyed) return anchorRect\n if (state.value.mode === 'caret') return caretRect(editor)\n const highlight = aiSelectionHighlightKey.getState(editor.state)\n const highlighted = highlight && resolveRange(editor.state, highlight.anchor)\n if (!highlighted || highlighted.from >= highlighted.to) return anchorRect\n const { view } = editor\n const start = view.coordsAtPos(highlighted.from, 1)\n const end = view.coordsAtPos(highlighted.to, -1)\n // Across lines the range spans the editor's width.\n const bounds = view.dom.getBoundingClientRect()\n const oneLine = end.top < start.bottom\n const left = oneLine ? start.left : bounds.left\n const right = oneLine ? end.right : bounds.right\n anchorRect = new DOMRect(left, start.top, Math.max(right - left, 1), end.bottom - start.top)\n return anchorRect\n },\n get contextElement() {\n return props.editor.view.dom\n },\n}\n\n// The caret's position is resolved from its anchor once per editor state (the anchor is measured every frame).\nlet caretAt: { state: EditorState, pos: number | null } | null = null\nfunction caretRect(editor: Editor): DOMRect {\n if (caretAt?.state !== editor.state) caretAt = { state: editor.state, pos: flow.range()?.from ?? null }\n if (caretAt.pos === null) return anchorRect\n const coords = editor.view.coordsAtPos(caretAt.pos)\n anchorRect = new DOMRect(coords.left, coords.top, 1, coords.bottom - coords.top)\n return anchorRect\n}\n\nconst preview = useEditor({\n editable: false,\n extensions: previewExtensions(props.editor),\n editorProps: { attributes: { class: 'tiptap ai-selection-preview' } },\n})\n\n// Coalesce streamed deltas: re-parse the Markdown at most every 80 ms; speaker lines show as segments, as applied.\nlet previewTimer: ReturnType<typeof setTimeout> | undefined\nwatch(() => state.value.output, () => {\n if (previewTimer) return\n previewTimer = setTimeout(() => {\n previewTimer = undefined\n const instance = preview.value\n if (!instance) return\n instance.commands.setContent({ type: 'doc', content: parseBlocks(props.editor, state.value.output).blocks }, { emitUpdate: false })\n }, 80)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(previewTimer)\n flow.dispose()\n preview.value?.destroy()\n})\n</script>\n\n<template>\n <BubbleMenu :editor=\"editor\" :plugin-key=\"AI_SELECTION_MENU_KEY\" :should-show=\"shouldShow\" :options=\"{ placement: 'top', offset: 8 }\" class=\"z-10\">\n <div class=\"flex rounded-md border border-[var(--sk-border)] bg-[var(--sk-bg)] p-0.5 shadow-md\">\n <UButton size=\"sm\" color=\"neutral\" variant=\"ghost\" icon=\"i-lucide-sparkles\" :label=\"t('ai.menu.trigger')\" @click=\"flow.openForSelection()\" />\n </div>\n </BubbleMenu>\n\n <UPopover\n :open=\"state.open\"\n :reference=\"reference\"\n :dismissible=\"state.picking\"\n :content=\"{ side: 'bottom', align: 'start', updatePositionStrategy: 'always', onOpenAutoFocus }\"\n :ui=\"{ content: 'w-[30rem] max-w-[calc(100vw-2rem)]' }\"\n @update:open=\"(value: boolean) => { if (!value) flow.close(state.mode === 'caret') }\"\n >\n <template #content>\n <div v-if=\"state.picking\" class=\"flex flex-col gap-1 p-2\">\n <!-- Caret mode generates blocks, selection mode rewrites text in the block: each has its own model slot. -->\n <div class=\"flex items-center justify-end gap-2 px-1 pb-1\">\n <span class=\"text-xs text-[var(--sk-muted)]\">{{ t('ai.menu.model') }}</span>\n <ModelQuickSwitch :slot=\"state.mode === 'caret' ? 'generate' : 'rewrite'\" size=\"xs\" class=\"w-56\" />\n </div>\n <div v-for=\"task in state.actions\" :key=\"task.id\" class=\"flex items-center gap-1\">\n <UButton\n :icon=\"task.icon\"\n :label=\"t(task.labelKey)\"\n color=\"neutral\"\n variant=\"ghost\"\n class=\"flex-1 justify-start\"\n :disabled=\"state.mode === 'caret' && task.needsSelection && !state.hasTextBefore\"\n @click=\"flow.start(task)\"\n />\n <USelect v-if=\"task.needsLanguage\" v-model=\"language\" :items=\"languageItems\" size=\"sm\" class=\"w-36\" :aria-label=\"t('ai.menu.targetLanguage')\" />\n </div>\n <UButton\n v-for=\"action in menuActions\"\n :key=\"action.id\"\n :icon=\"action.icon\"\n :label=\"action.label\"\n color=\"neutral\"\n variant=\"ghost\"\n class=\"justify-start\"\n :loading=\"state.runningAction === action.id\"\n :disabled=\"state.runningAction !== null && state.runningAction !== action.id\"\n @click=\"flow.runAction(action)\"\n />\n <form class=\"mt-1 flex gap-1 border-t border-[var(--sk-border)] pt-2\" @submit.prevent=\"flow.submitPrompt()\">\n <UInput ref=\"promptInput\" v-model=\"prompt\" :icon=\"state.freePrompt.icon\" :placeholder=\"state.freePrompt.promptLabelKey && t(state.freePrompt.promptLabelKey)\" class=\"flex-1\" />\n <UButton type=\"submit\" icon=\"i-lucide-arrow-up\" :disabled=\"!state.prompt.trim()\" :aria-label=\"t('ai.menu.sendInstruction')\" />\n </form>\n </div>\n\n <div v-else class=\"flex flex-col gap-3 p-3\">\n <div class=\"flex items-center gap-2 text-sm font-medium\">\n <UIcon v-if=\"state.request\" :name=\"state.request.task.icon\" class=\"size-4\" />\n <span class=\"flex-1 truncate\">{{ requestLabel }}</span>\n <UIcon v-if=\"state.status === 'streaming'\" name=\"i-lucide-loader-circle\" class=\"size-4 animate-spin text-[var(--sk-muted)]\" />\n </div>\n <UAlert v-if=\"state.error\" color=\"error\" variant=\"subtle\" :title=\"state.error\" />\n <div class=\"max-h-72 min-h-16 overflow-auto rounded-md border border-[var(--sk-border)] p-3 text-sm\">\n <EditorContent v-if=\"preview\" :editor=\"preview\" />\n </div>\n <div class=\"flex flex-wrap justify-end gap-2\">\n <UButton v-if=\"state.status === 'streaming'\" size=\"sm\" color=\"neutral\" variant=\"ghost\" :label=\"t('ai.menu.cancel')\" @click=\"flow.abort()\" />\n <UButton size=\"sm\" color=\"neutral\" variant=\"ghost\" :label=\"t('ai.menu.discard')\" @click=\"flow.close(true)\" />\n <UButton\n size=\"sm\"\n color=\"neutral\"\n variant=\"outline\"\n icon=\"i-lucide-rotate-ccw\"\n :label=\"t('ai.menu.retry')\"\n :disabled=\"state.status === 'streaming'\"\n @click=\"flow.retry()\"\n />\n <UButton v-if=\"state.mode === 'caret'\" size=\"sm\" icon=\"i-lucide-text-cursor-input\" :label=\"t('ai.menu.insert')\" :disabled=\"!state.canApply\" @click=\"flow.insertAtCaret()\" />\n <template v-else>\n <UButton\n size=\"sm\"\n color=\"neutral\"\n variant=\"outline\"\n icon=\"i-lucide-list-plus\"\n :label=\"t('ai.menu.insertBelow')\"\n :disabled=\"!state.canApply\"\n @click=\"flow.apply('insert-below')\"\n />\n <UButton size=\"sm\" icon=\"i-lucide-replace\" :label=\"t('ai.menu.replace')\" :disabled=\"!state.canApply\" @click=\"flow.apply('replace')\" />\n </template>\n </div>\n </div>\n </template>\n </UPopover>\n</template>\n"],"mappings":"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"}
|