@ecsplico/editor 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +4 -3
  3. package/dist/adapters/anchors.d.ts +80 -0
  4. package/dist/adapters/anchors.js +270 -0
  5. package/dist/adapters/anchors.js.map +1 -0
  6. package/dist/adapters/comments/client.d.ts +64 -0
  7. package/dist/adapters/comments/client.js +536 -0
  8. package/dist/adapters/comments/client.js.map +1 -0
  9. package/dist/adapters/comments/drizzle.d.ts +430 -0
  10. package/dist/adapters/comments/drizzle.js +236 -0
  11. package/dist/adapters/comments/drizzle.js.map +1 -0
  12. package/dist/adapters/comments/errors.d.ts +9 -0
  13. package/dist/adapters/comments/errors.js +19 -0
  14. package/dist/adapters/comments/errors.js.map +1 -0
  15. package/dist/adapters/comments/json.d.ts +19 -0
  16. package/dist/adapters/comments/json.js +94 -0
  17. package/dist/adapters/comments/json.js.map +1 -0
  18. package/dist/adapters/comments/record.js +78 -0
  19. package/dist/adapters/comments/record.js.map +1 -0
  20. package/dist/adapters/comments/server.d.ts +23 -0
  21. package/dist/adapters/comments/server.js +102 -0
  22. package/dist/adapters/comments/server.js.map +1 -0
  23. package/dist/adapters/comments/types.d.ts +109 -0
  24. package/dist/adapters/documents.js +108 -0
  25. package/dist/adapters/documents.js.map +1 -0
  26. package/dist/adapters/drizzle.d.ts +293 -0
  27. package/dist/adapters/drizzle.js +157 -0
  28. package/dist/adapters/drizzle.js.map +1 -0
  29. package/dist/adapters/files.js +30 -0
  30. package/dist/adapters/files.js.map +1 -0
  31. package/dist/adapters/forge.d.ts +58 -0
  32. package/dist/adapters/forge.js +91 -0
  33. package/dist/adapters/forge.js.map +1 -0
  34. package/dist/adapters/index.d.ts +13 -0
  35. package/dist/adapters/index.js +0 -0
  36. package/dist/adapters/json.d.ts +24 -0
  37. package/dist/adapters/json.js +128 -0
  38. package/dist/adapters/json.js.map +1 -0
  39. package/dist/adapters/nitro.d.ts +61 -0
  40. package/dist/adapters/nitro.js +212 -0
  41. package/dist/adapters/nitro.js.map +1 -0
  42. package/dist/adapters/routes.js +16 -0
  43. package/dist/adapters/routes.js.map +1 -0
  44. package/dist/adapters/shared.d.ts +37 -0
  45. package/dist/adapters/shared.js +32 -0
  46. package/dist/adapters/shared.js.map +1 -0
  47. package/dist/adapters/vue.d.ts +67 -0
  48. package/dist/adapters/vue.js +187 -0
  49. package/dist/adapters/vue.js.map +1 -0
  50. package/dist/code/CodeSurface.vue.d.ts +3 -3
  51. package/dist/core/doc-anchors.d.ts +5 -0
  52. package/dist/core/doc-anchors.js +4 -3
  53. package/dist/core/doc-anchors.js.map +1 -1
  54. package/dist/core/editor-ui.js +57 -1
  55. package/dist/core/editor-ui.js.map +1 -1
  56. package/dist/core/surface-session.js +139 -0
  57. package/dist/core/surface-session.js.map +1 -0
  58. package/dist/document/DocumentSurface.d.ts +1 -1
  59. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  60. package/dist/features/ai/agent-op-review.d.ts +2 -2
  61. package/dist/features/ai/agent-op-review.js.map +1 -1
  62. package/dist/features/ai/agent-ops.d.ts +1 -1
  63. package/dist/features/ai/agent-ops.js +9 -6
  64. package/dist/features/ai/agent-ops.js.map +1 -1
  65. package/dist/features/ai/ai-apply.d.ts +1 -1
  66. package/dist/features/ai/ai-review.js +10 -1
  67. package/dist/features/ai/ai-review.js.map +1 -1
  68. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  69. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  71. package/dist/features/ai/components/AiSidebar.js +2 -2
  72. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  73. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  74. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  75. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  76. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  77. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  78. package/dist/features/ai/document-chat.d.ts +3 -2
  79. package/dist/features/ai/document-chat.js +16 -19
  80. package/dist/features/ai/document-chat.js.map +1 -1
  81. package/dist/features/ai/index.d.ts +5 -3
  82. package/dist/features/ai/index.js +4 -2
  83. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  84. package/dist/features/ai/protocol/agent-ops.js +65 -39
  85. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  86. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  87. package/dist/features/ai/protocol/block-lines.js +177 -0
  88. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  89. package/dist/features/ai/protocol/chat.js +10 -10
  90. package/dist/features/ai/protocol/chat.js.map +1 -1
  91. package/dist/features/ai/protocol/index.d.ts +3 -2
  92. package/dist/features/ai/protocol/index.js +3 -2
  93. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  94. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  95. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  96. package/dist/features/ai/react/AiSidebar.js +2 -2
  97. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  98. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  99. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  100. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  101. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  102. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  103. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  104. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  105. package/dist/features/ai/selection-flow.d.ts +133 -0
  106. package/dist/features/ai/selection-flow.js +283 -0
  107. package/dist/features/ai/selection-flow.js.map +1 -0
  108. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  109. package/dist/features/ai/use-document-chat.d.ts +1 -1
  110. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  111. package/dist/features/authors/feature.js +1 -1
  112. package/dist/features/authors/index.js +1 -1
  113. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  114. package/dist/features/comments/components/CommentBubble.js +1 -1
  115. package/dist/features/comments/components/CommentComposer.js +1 -1
  116. package/dist/features/comments/components/CommentThreadView.js +5 -2
  117. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  118. package/dist/features/comments/components/CommentsMargin.js +108 -230
  119. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  120. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  121. package/dist/features/comments/feature.js +1 -1
  122. package/dist/features/comments/index.d.ts +4 -2
  123. package/dist/features/comments/index.js +4 -2
  124. package/dist/features/comments/margin-controller.d.ts +44 -0
  125. package/dist/features/comments/margin-controller.js +132 -0
  126. package/dist/features/comments/margin-controller.js.map +1 -0
  127. package/dist/features/comments/margin-layout.d.ts +55 -0
  128. package/dist/features/comments/margin-layout.js +79 -0
  129. package/dist/features/comments/margin-layout.js.map +1 -0
  130. package/dist/features/comments/model.d.ts +8 -0
  131. package/dist/features/comments/model.js +60 -34
  132. package/dist/features/comments/model.js.map +1 -1
  133. package/dist/features/comments/react/CommentBubble.js +1 -1
  134. package/dist/features/comments/react/CommentComposer.js +1 -1
  135. package/dist/features/comments/react/CommentThreadView.js +2 -2
  136. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  137. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  138. package/dist/features/comments/react/CommentsMargin.js +85 -251
  139. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  140. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  141. package/dist/features/comments/react/use-comments.js +1 -1
  142. package/dist/features/comments/react/use-margin-layout.js +45 -0
  143. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  144. package/dist/features/comments/react.d.ts +1 -1
  145. package/dist/features/comments/schema.d.ts +23 -0
  146. package/dist/features/comments/schema.js.map +1 -1
  147. package/dist/features/comments/snapshot-equality.js +40 -0
  148. package/dist/features/comments/snapshot-equality.js.map +1 -0
  149. package/dist/features/comments/thread-anchor.js +33 -0
  150. package/dist/features/comments/thread-anchor.js.map +1 -0
  151. package/dist/features/comments/use-comments.d.ts +3 -1
  152. package/dist/features/comments/use-comments.js +1 -0
  153. package/dist/features/comments/use-comments.js.map +1 -1
  154. package/dist/features/comments/use-margin-layout.js +29 -0
  155. package/dist/features/comments/use-margin-layout.js.map +1 -0
  156. package/dist/features/comments/vue.d.ts +1 -1
  157. package/dist/features/composites/accepted-children.d.ts +21 -0
  158. package/dist/features/composites/accepted-children.js +126 -0
  159. package/dist/features/composites/accepted-children.js.map +1 -0
  160. package/dist/features/composites/components/CompositeDetails.js +1 -1
  161. package/dist/features/composites/components/CompositeInspector.js +38 -164
  162. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  163. package/dist/features/composites/feature.d.ts +2 -1
  164. package/dist/features/composites/feature.js +8 -2
  165. package/dist/features/composites/feature.js.map +1 -1
  166. package/dist/features/composites/index.d.ts +5 -3
  167. package/dist/features/composites/index.js +8 -6
  168. package/dist/features/composites/inspector-model.d.ts +97 -0
  169. package/dist/features/composites/inspector-model.js +255 -0
  170. package/dist/features/composites/inspector-model.js.map +1 -0
  171. package/dist/features/composites/kinds.d.ts +11 -0
  172. package/dist/features/composites/kinds.js +17 -2
  173. package/dist/features/composites/kinds.js.map +1 -1
  174. package/dist/features/composites/outline.d.ts +18 -5
  175. package/dist/features/composites/outline.js +44 -12
  176. package/dist/features/composites/outline.js.map +1 -1
  177. package/dist/features/composites/react/CompositeDetails.js +1 -1
  178. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  179. package/dist/features/composites/react/CompositeInspector.js +21 -180
  180. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  181. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  182. package/dist/features/composites/react/CompositeView.js +1 -1
  183. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  184. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  185. package/dist/features/composites/schema.d.ts +2 -2
  186. package/dist/features/composites/schema.js +2 -2
  187. package/dist/features/composites/schema.js.map +1 -1
  188. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  189. package/dist/features/composites/use-composite-inspector.js +41 -0
  190. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  191. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  192. package/dist/features/media/index.d.ts +1 -1
  193. package/dist/features/media/vue.d.ts +1 -1
  194. package/dist/features/mentions/index.d.ts +3 -3
  195. package/dist/features/mentions/index.js +3 -3
  196. package/dist/features/mentions/schema.d.ts +1 -1
  197. package/dist/features/mentions/schema.js +1 -1
  198. package/dist/features/themes/index.d.ts +1 -1
  199. package/dist/features/themes/index.js +1 -1
  200. package/dist/features/transcript/feature.js +1 -1
  201. package/dist/features/transcript/index.js +2 -2
  202. package/dist/i18n/de/ai.js +2 -0
  203. package/dist/i18n/de/ai.js.map +1 -1
  204. package/dist/i18n/de/composites.js +8 -1
  205. package/dist/i18n/de/composites.js.map +1 -1
  206. package/dist/i18n/de/tree.js +14 -1
  207. package/dist/i18n/de/tree.js.map +1 -1
  208. package/dist/i18n/de.d.ts +22 -0
  209. package/dist/i18n/en/ai.js +2 -0
  210. package/dist/i18n/en/ai.js.map +1 -1
  211. package/dist/i18n/en/composites.js +8 -1
  212. package/dist/i18n/en/composites.js.map +1 -1
  213. package/dist/i18n/en/tree.js +14 -1
  214. package/dist/i18n/en/tree.js.map +1 -1
  215. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  216. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  217. package/dist/integrations/composite-ai/index.js +2 -2
  218. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  219. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  220. package/dist/markdown/MarkdownSurface.js +2 -2
  221. package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
  222. package/dist/markdown/index.d.ts +2 -2
  223. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  224. package/dist/react/EditorSurface.d.ts +1 -1
  225. package/dist/react/EditorSurface.js +43 -149
  226. package/dist/react/EditorSurface.js.map +1 -1
  227. package/dist/react/EditorToolbar.js +3 -40
  228. package/dist/react/EditorToolbar.js.map +1 -1
  229. package/dist/schema/advanced.js +2 -2
  230. package/dist/server/adapter.d.ts +2 -2
  231. package/dist/server/collab.js +1 -1
  232. package/dist/server/handlers/ai.d.ts +4 -2
  233. package/dist/server/handlers/ai.js +11 -9
  234. package/dist/server/handlers/ai.js.map +1 -1
  235. package/dist/server/handlers/context.d.ts +1 -1
  236. package/dist/server/handlers/editor.d.ts +1 -1
  237. package/dist/server/handlers/editor.js +2 -2
  238. package/dist/server/host.d.ts +1 -1
  239. package/dist/server/index.d.ts +2 -2
  240. package/dist/settings/index.d.ts +1 -1
  241. package/dist/styles/tokens.css +1 -1
  242. package/dist/tree/DocumentTree.js +159 -387
  243. package/dist/tree/DocumentTree.js.map +1 -1
  244. package/dist/tree/drop-rules.js +37 -0
  245. package/dist/tree/drop-rules.js.map +1 -0
  246. package/dist/tree/react/DocumentTree.d.ts +2 -2
  247. package/dist/tree/react/DocumentTree.js +135 -397
  248. package/dist/tree/react/DocumentTree.js.map +1 -1
  249. package/dist/tree/react/use-tree-model.js +129 -0
  250. package/dist/tree/react/use-tree-model.js.map +1 -0
  251. package/dist/tree/react.js +1 -1
  252. package/dist/tree/tree-host.js +100 -0
  253. package/dist/tree/tree-host.js.map +1 -0
  254. package/dist/tree/tree-items.js +22 -43
  255. package/dist/tree/tree-items.js.map +1 -1
  256. package/dist/tree/tree-model.js +580 -0
  257. package/dist/tree/tree-model.js.map +1 -0
  258. package/dist/tree/tree-rows.js +77 -0
  259. package/dist/tree/tree-rows.js.map +1 -0
  260. package/dist/tree/use-tree-model.js +94 -0
  261. package/dist/tree/use-tree-model.js.map +1 -0
  262. package/dist/tree/vue.js +1 -1
  263. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  264. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  265. package/dist/vue/components/EditorSurface.js +27 -87
  266. package/dist/vue/components/EditorSurface.js.map +1 -1
  267. package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
  268. package/dist/vue/components/EditorToolbar.js +7 -44
  269. package/dist/vue/components/EditorToolbar.js.map +1 -1
  270. package/dist/vue/context.d.ts +1 -1
  271. package/dist/vue/context.js +1 -1
  272. package/dist/vue/context.js.map +1 -1
  273. package/dist/vue/use-editor-ui.d.ts +1 -1
  274. package/dist/vue/use-notifier.d.ts +1 -1
  275. package/docs/integration.md +97 -4
  276. package/docs/llms.md +9 -1
  277. package/docs/usage.md +10 -5
  278. package/package.json +67 -5
@@ -1,22 +1,18 @@
1
- import { ABILITY } from "../../../core/schema/abilities.js";
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 { captureRange, releaseRanges, resolveRange } from "../../../core/doc-anchors.js";
6
- import { selectionRange } from "../../../core/selection-range.js";
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 { AI_LANGUAGE_KEYS } from "../protocol/models.js";
11
- import { AI_LIMITS, AI_TASKS } from "../protocol/tasks.js";
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, ref, renderList, shallowRef, toDisplayString, unref, useTemplateRef, watch, withCtx, withModifiers } from "vue";
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 { status, output, error, run, abort, reset } = useAiAssistant(() => props.editor);
55
- const quickActions = Object.values(AI_TASKS).filter((task) => task.selectionMenu && !task.needsPrompt);
56
- const custom = AI_TASKS.custom;
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 { settings } = useEditorSettings();
62
- /** Target language of „Übersetzen“: the settings' default each time the menu opens, changeable per call. */
63
- const language = ref(settings.value.ai.translateLanguage);
64
- const prompt = ref("");
65
- /** 'selection': rewrites the captured selection; 'caret': writes new text at the captured (collapsed) caret. */
66
- const mode = ref("selection");
67
- const caretActions = Object.values(AI_TASKS).filter((task) => task.caretMenu);
68
- const actions = computed(() => mode.value === "caret" ? caretActions : quickActions);
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
- if (!request.value) return void 0;
81
- const task = t(request.value.task.labelKey);
82
- return request.value.language ? t("ai.menu.requestWithLanguage", {
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.value.language])
79
+ language: t(AI_LANGUAGE_KEYS[request.language])
85
80
  }) : task;
86
81
  });
87
- const menuActions = computed(() => {
88
- const resolved = range.value && resolveRange(props.editor.state, range.value);
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 (mode.value !== "caret") return;
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 (mode.value === "caret") return caretRect(editor);
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.value ? resolveRange(editor.state, range.value)?.from ?? null : null
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, output.value).blocks
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
- abort();
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": MENU_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: openMenu
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: range.value !== null,
167
+ }, 8, ["editor", "plugin-key"]), createVNode(unref(UPopover), {
168
+ open: unref(state).open,
343
169
  reference,
344
- dismissible: picking.value,
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[7] || (_cache[7] = (value) => {
353
- if (!value) close(mode.value === "caret");
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.value ? (openBlock(), createElementBlock("div", _hoisted_2, [
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.value === "caret" ? "generate" : "rewrite",
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.value, (task) => {
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.value === "caret" && task.needsSelection && !hasTextBefore.value,
374
- onClick: ($event) => start(task, task.needsLanguage ? { language: language.value } : {})
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[0] || (_cache[0] = ($event) => language.value = $event),
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.value === action.id,
403
- disabled: runningAction.value !== null && runningAction.value !== action.id,
404
- onClick: ($event) => runMenuAction(action)
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[2] || (_cache[2] = withModifiers(($event) => prompt.value.trim() && start(freePrompt.value, { prompt: prompt.value }), ["prevent"]))
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[1] || (_cache[1] = ($event) => prompt.value = $event),
421
- icon: freePrompt.value.icon,
422
- placeholder: freePrompt.value.promptLabelKey && unref(t)(freePrompt.value.promptLabelKey),
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.value.trim(),
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.value ? (openBlock(), createBlock(unref(UIcon), {
262
+ unref(state).request ? (openBlock(), createBlock(unref(UIcon), {
437
263
  key: 0,
438
- name: request.value.task.icon,
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) === "streaming" ? (openBlock(), createBlock(unref(UIcon), {
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) ? (openBlock(), createBlock(unref(UAlert), {
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) === "streaming" ? (openBlock(), createBlock(unref(UButton), {
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[3] || (_cache[3] = ($event) => unref(abort)())
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[4] || (_cache[4] = ($event) => close(true))
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) === "streaming",
481
- onClick: generate
306
+ disabled: unref(state).status === "streaming",
307
+ onClick: _cache[6] || (_cache[6] = ($event) => unref(flow).retry())
482
308
  }, null, 8, ["label", "disabled"]),
483
- mode.value === "caret" ? (openBlock(), createBlock(unref(UButton), {
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.value,
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.value,
497
- onClick: _cache[5] || (_cache[5] = ($event) => apply("insert-below"))
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.value,
503
- onClick: _cache[6] || (_cache[6] = ($event) => apply("replace"))
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":"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"}
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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"}