@liveblocks/react-tiptap 2.16.0-toolbars5 → 2.16.1-ai

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 (48) hide show
  1. package/dist/LiveblocksExtension.js +137 -21
  2. package/dist/LiveblocksExtension.js.map +1 -1
  3. package/dist/LiveblocksExtension.mjs +135 -19
  4. package/dist/LiveblocksExtension.mjs.map +1 -1
  5. package/dist/ai/AiExtension.js +288 -0
  6. package/dist/ai/AiExtension.js.map +1 -0
  7. package/dist/ai/AiExtension.mjs +285 -0
  8. package/dist/ai/AiExtension.mjs.map +1 -0
  9. package/dist/ai/AiToolbar.js +540 -0
  10. package/dist/ai/AiToolbar.js.map +1 -0
  11. package/dist/ai/AiToolbar.mjs +537 -0
  12. package/dist/ai/AiToolbar.mjs.map +1 -0
  13. package/dist/comments/CommentsExtension.js +10 -15
  14. package/dist/comments/CommentsExtension.js.map +1 -1
  15. package/dist/comments/CommentsExtension.mjs +11 -16
  16. package/dist/comments/CommentsExtension.mjs.map +1 -1
  17. package/dist/comments/FloatingComposer.js +5 -3
  18. package/dist/comments/FloatingComposer.js.map +1 -1
  19. package/dist/comments/FloatingComposer.mjs +6 -4
  20. package/dist/comments/FloatingComposer.mjs.map +1 -1
  21. package/dist/index.d.mts +54 -14
  22. package/dist/index.d.ts +54 -14
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +1 -0
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/toolbar/FloatingToolbar.js +7 -0
  28. package/dist/toolbar/FloatingToolbar.js.map +1 -1
  29. package/dist/toolbar/FloatingToolbar.mjs +7 -0
  30. package/dist/toolbar/FloatingToolbar.mjs.map +1 -1
  31. package/dist/toolbar/Toolbar.js +35 -1
  32. package/dist/toolbar/Toolbar.js.map +1 -1
  33. package/dist/toolbar/Toolbar.mjs +36 -2
  34. package/dist/toolbar/Toolbar.mjs.map +1 -1
  35. package/dist/types.js.map +1 -1
  36. package/dist/types.mjs.map +1 -1
  37. package/dist/utils.js +33 -5
  38. package/dist/utils.js.map +1 -1
  39. package/dist/utils.mjs +29 -4
  40. package/dist/utils.mjs.map +1 -1
  41. package/dist/version.js +1 -1
  42. package/dist/version.js.map +1 -1
  43. package/dist/version.mjs +1 -1
  44. package/dist/version.mjs.map +1 -1
  45. package/package.json +7 -6
  46. package/src/styles/index.css +319 -3
  47. package/styles.css +1 -1
  48. package/styles.css.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiExtension.mjs","sources":["../../src/ai/AiExtension.ts"],"sourcesContent":["import type { LiveblocksYjsProvider } from \"@liveblocks/yjs\";\nimport type { Editor } from \"@tiptap/core\";\nimport { Extension } from \"@tiptap/core\";\nimport { Fragment, Slice } from \"@tiptap/pm/model\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport { Plugin } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport {\n ySyncPluginKey,\n yXmlFragmentToProseMirrorFragment,\n} from \"y-prosemirror\";\nimport type { Snapshot } from \"yjs\";\nimport {\n createDocFromSnapshot,\n emptySnapshot,\n equalSnapshots,\n snapshot,\n} from \"yjs\";\n\nimport {\n AI_TOOLBAR_SELECTION_PLUGIN,\n type AiCommands,\n type AiExtensionOptions,\n type AiExtensionStorage,\n type AiToolbarOutput,\n type AiToolbarState,\n type LiveblocksExtensionStorage,\n type YSyncPluginState,\n} from \"../types\";\n\nconst DEFAULT_AI_NAME = \"AI\";\nexport const DEFAULT_STATE: AiToolbarState = { phase: \"closed\" };\n\nfunction getYjsBinding(editor: Editor) {\n return (ySyncPluginKey.getState(editor.view.state) as YSyncPluginState)\n .binding;\n}\n\nfunction getLiveblocksYjsProvider(editor: Editor) {\n return (\n editor.extensionStorage.liveblocksExtension as\n | LiveblocksExtensionStorage\n | undefined\n )?.provider as LiveblocksYjsProvider | undefined;\n}\n\nexport const AiExtension = Extension.create<\n AiExtensionOptions,\n AiExtensionStorage\n>({\n name: \"liveblocksAi\",\n addOptions() {\n return {\n doc: undefined,\n pud: undefined,\n\n // The actual default resolver is set in LiveblocksExtension via AiExtension.configure()\n resolveAiPrompt: () => Promise.reject(),\n name: DEFAULT_AI_NAME,\n };\n },\n addStorage() {\n return {\n state: DEFAULT_STATE,\n name: this.options.name,\n };\n },\n onCreate() {\n // Turn off gc for snapshots to work\n // TODO: remove this later, we only need to compare two full copies\n // if (this.options.doc) {\n // this.options.doc.gc = false;\n // }\n },\n addCommands() {\n return {\n askAi: (prompt) => () => {\n if (typeof prompt === \"string\") {\n (\n this.editor.commands as unknown as AiCommands\n ).$startAiToolbarThinking(prompt);\n } else {\n (\n this.editor.commands as unknown as AiCommands\n ).$openAiToolbarAsking();\n }\n\n return true;\n },\n\n $acceptAiToolbarOutput:\n () =>\n // todo: figure out why I needed to manually type this\n ({ tr }: { tr: Transaction }) => {\n const currentState = this.storage.state;\n if (currentState.phase !== \"reviewing\") {\n return false;\n }\n const binding = getYjsBinding(this.editor);\n if (!binding) {\n return false;\n }\n\n const fragmentContent = yXmlFragmentToProseMirrorFragment(\n binding.type,\n this.editor.state.schema\n );\n tr.setMeta(\"addToHistory\", false);\n tr.replace(\n 0,\n this.editor.state.doc.content.size,\n new Slice(Fragment.from(fragmentContent), 0, 0)\n );\n tr.setMeta(ySyncPluginKey, {\n snapshot: null,\n prevSnapshot: null,\n });\n\n // TODO: move this cleanup to somewhere that closeAIToolbar can share\n getLiveblocksYjsProvider(this.editor)?.unpause();\n this.editor.setEditable(true);\n this.storage.snapshot = undefined;\n this.storage.state = { phase: \"closed\" };\n return true;\n },\n\n $closeAiToolbar:\n () =>\n // todo: figure out why I needed to manually type this\n ({ tr }: { tr: Transaction }) => {\n const currentState = this.storage.state;\n\n // 1. If in \"thinking\" phase, cancel the current AI request\n if (currentState.phase === \"thinking\") {\n currentState.abortController.abort();\n }\n\n // 2. If in \"thinking\" or \"reviewing\" phase, revert the editor if possible and unblock it\n if (\n currentState.phase === \"thinking\" ||\n currentState.phase === \"reviewing\"\n ) {\n if (this.storage.snapshot) {\n const binding = getYjsBinding(this.editor);\n\n if (binding) {\n binding.mapping.clear();\n\n const docFromSnapshot = createDocFromSnapshot(\n binding.doc,\n this.storage.snapshot\n );\n const type = docFromSnapshot.getXmlFragment(\"default\"); // TODO: field\n const fragmentContent = yXmlFragmentToProseMirrorFragment(\n type,\n this.editor.state.schema\n );\n\n tr.setMeta(\"addToHistory\", false);\n tr.replace(\n 0,\n this.editor.state.doc.content.size,\n new Slice(Fragment.from(fragmentContent), 0, 0)\n );\n tr.setMeta(ySyncPluginKey, {\n snapshot: null,\n prevSnapshot: null,\n });\n\n getLiveblocksYjsProvider(this.editor)?.unpause();\n\n if (this.options.doc) {\n this.options.doc.gc = true;\n }\n\n this.storage.snapshot = undefined;\n }\n }\n\n this.editor.setEditable(true);\n }\n\n // 4. Set to \"closed\" phase\n this.storage.state = { phase: \"closed\" };\n\n return true;\n },\n\n $openAiToolbarAsking: () => () => {\n const currentState = this.storage.state;\n\n // 1. If NOT in \"closed\" phase, do nothing\n if (currentState.phase !== \"closed\") {\n return false;\n }\n\n // 2. Blur the editor if needed\n if (this.editor.isFocused) {\n this.editor.commands.blur();\n }\n\n // 3. Set to \"asking\" phase\n this.storage.state = {\n phase: \"asking\",\n\n // Initialize the custom prompt as empty\n customPrompt: \"\",\n };\n\n return true;\n },\n\n $startAiToolbarThinking: (prompt: string) => () => {\n const currentState = this.storage.state;\n\n // 1. If in \"thinking\" phase already, do nothing\n if (currentState.phase === \"thinking\") {\n return false;\n }\n\n if (currentState.phase === \"reviewing\") {\n // TODO: this is a retry, we should actually retry and start thinking again\n return (\n this.editor.commands as unknown as AiCommands\n ).$closeAiToolbar();\n }\n\n // 2. Blur the editor if needed\n if (this.editor.isFocused) {\n this.editor.commands.blur();\n }\n\n const abortController = new AbortController();\n const provider = getLiveblocksYjsProvider(this.editor);\n\n // 3. Set to \"thinking\" phase\n this.storage.state = {\n phase: \"thinking\",\n customPrompt: currentState.customPrompt ?? \"\",\n prompt,\n abortController,\n };\n\n // 4. Block the editor\n this.editor.setEditable(false);\n\n // 5. Execute the AI request\n const executeAiRequest = async () => {\n await provider?.pause();\n const { from, to } = this.editor.state.selection.empty\n ? {\n // TODO: this is a hack to get the context around the selection, we need to improve this\n from: Math.max(this.editor.state.selection.to - 30, 0),\n to: this.editor.state.selection.to,\n }\n : this.editor.state.selection;\n return this.options.resolveAiPrompt({\n prompt,\n selectionText: this.editor.state.doc.textBetween(from, to, \" \"),\n /*\n TODO: This needs a maximum to avoid overloading context, for now I've arbitrailiry chosen 3000\n characters but this will need to be improved, probably using word boundary of some sort (languages can make that tricky)\n as well as choosing text around the selection, so before/after.\n */\n context: this.editor.getText().slice(0, 3_000),\n signal: abortController.signal,\n });\n };\n\n executeAiRequest()\n .then((output) => {\n if (abortController.signal.aborted) {\n return;\n }\n\n // 5.a. If the AI request succeeds, set to \"reviewing\" phase with the output\n (\n this.editor.commands as unknown as AiCommands\n )._handleAiToolbarThinkingSuccess({\n type: output.type,\n text: output.content,\n });\n })\n .catch((error) => {\n if (abortController.signal.aborted) {\n return;\n }\n\n // 5.b. If the AI request fails, set to \"asking\" phase with error\n (\n this.editor.commands as unknown as AiCommands\n )._handleAiToolbarThinkingError(error as Error);\n });\n\n return true;\n },\n\n $cancelAiToolbarThinking: () => () => {\n const currentState = this.storage.state;\n\n // 1. If NOT in \"thinking\" phase, do nothing\n if (currentState.phase !== \"thinking\") {\n return false;\n }\n\n // 2. Cancel the current AI request\n currentState.abortController.abort();\n\n // 3. Unblock the editor\n this.editor.setEditable(true);\n\n // 4. Set to \"asking\" phase\n this.storage.state = {\n phase: \"asking\",\n // If the custom prompt is different than the prompt, reset it\n customPrompt:\n currentState.prompt === currentState.customPrompt\n ? currentState.customPrompt\n : \"\",\n };\n\n return true;\n },\n\n _handleAiToolbarThinkingSuccess: (output: AiToolbarOutput) => () => {\n const currentState = this.storage.state;\n\n // 1. If NOT in \"thinking\" phase, do nothing\n if (currentState.phase !== \"thinking\" || !this.options.doc) {\n return false;\n }\n\n // TODO: Diff vs other output types\n // 2. If the output is a diff, apply it to the editor\n if ([\"modification\", \"insert\"].includes(output.type)) {\n this.options.doc.gc = false;\n this.storage.snapshot = snapshot(this.options.doc);\n // TODO: We now rely on editor.state.selection but this breaks it, should we update editor.state.selection or keep our own selection?\n // settimeout will make this execute after the current transaction is committed, which is returned by the insert content command.\n setTimeout(() => {\n if (this.storage.snapshot) {\n (\n this.editor.commands as unknown as AiCommands\n )._renderAiToolbarDiffInEditor(this.storage.snapshot);\n }\n }, 100);\n } else {\n // \"Other\"\n return true;\n }\n\n const { from, to } = this.editor.state.selection;\n // if the selection is empty, insert at the end of the selection\n const contentTarget =\n this.editor.state.selection.empty || output.type === \"insert\"\n ? this.editor.state.selection.to\n : {\n from,\n to,\n };\n\n // when inserting, use the \"to\" position, otherwise when modifing, use the \"from\" position\n const targetTo = output.type === \"insert\" ? to : from;\n\n // 3. Set to \"reviewing\" phase with the output\n this.storage.state = {\n phase: \"reviewing\",\n customPrompt: \"\",\n prompt: currentState.prompt,\n output,\n contentTarget: { from, to: targetTo + output.text.length }, // take into account the new length with output\n };\n\n // 4. insert the output.\n return this.editor.commands.insertContentAt(contentTarget, output.text);\n },\n\n _handleAiToolbarThinkingError: (error: Error) => () => {\n const currentState = this.storage.state;\n\n // 1. If NOT in \"thinking\" phase, do nothing\n if (currentState.phase !== \"thinking\") {\n return false;\n }\n\n // 2. Unblock the editor\n this.editor.setEditable(true);\n\n // 3. Set to \"asking\" phase with error\n this.storage.state = {\n phase: \"asking\",\n // If the custom prompt is different than the prompt, reset it\n customPrompt:\n currentState.prompt === currentState.customPrompt\n ? currentState.customPrompt\n : \"\",\n // TODO: Improve error handling\n error,\n };\n\n return true;\n },\n\n _updateAiToolbarCustomPrompt:\n (customPrompt: string | ((currentCustomPrompt: string) => string)) =>\n () => {\n const currentState = this.storage.state;\n\n // 1. If NOT in a phase with a custom prompt, do nothing\n if (typeof currentState.customPrompt !== \"string\") {\n return false;\n }\n\n // 2. Update the custom prompt\n this.storage.state.customPrompt =\n typeof customPrompt === \"function\"\n ? customPrompt(currentState.customPrompt)\n : customPrompt;\n\n return true;\n },\n\n _renderAiToolbarDiffInEditor: (previous?: Snapshot) => () => {\n if (!this.options.doc) {\n return false;\n }\n\n const previousSnapshot: Snapshot = previous ?? emptySnapshot;\n const currentSnapshot = snapshot(this.options.doc);\n\n if (equalSnapshots(previousSnapshot, currentSnapshot)) {\n return true;\n }\n\n const binding = getYjsBinding(this.editor);\n\n if (binding) {\n binding.renderSnapshot(currentSnapshot, previousSnapshot);\n return true;\n }\n\n return false;\n },\n };\n },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: AI_TOOLBAR_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc, selection }) => {\n if (this.storage.state.phase === \"closed\") {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = selection;\n const decorations: Decoration[] = [\n Decoration.inline(from, to, {\n class: \"lb-root lb-selection lb-tiptap-active-selection\",\n }),\n ];\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n ];\n },\n});\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,540 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var reactDom = require('@floating-ui/react-dom');
5
+ var _private$1 = require('@liveblocks/react/_private');
6
+ var _private = require('@liveblocks/react-ui/_private');
7
+ var react$1 = require('@tiptap/react');
8
+ var cmdk = require('cmdk');
9
+ var react = require('react');
10
+ var reactDom$1 = require('react-dom');
11
+ var classnames = require('../classnames.js');
12
+ var context = require('../context.js');
13
+ var utils = require('../utils.js');
14
+ var AiExtension = require('./AiExtension.js');
15
+
16
+ const AI_TOOLBAR_COLLISION_PADDING = 10;
17
+ const AiToolbarContext = react.createContext(null);
18
+ function useAiToolbarContext() {
19
+ const context = react.useContext(AiToolbarContext);
20
+ if (!context) {
21
+ throw new Error("useAiToolbarContext must be used within an AiToolbar");
22
+ }
23
+ return context;
24
+ }
25
+ function tiptapFloating(editor) {
26
+ return {
27
+ name: "tiptap",
28
+ options: editor,
29
+ fn({ elements }) {
30
+ if (!editor) {
31
+ return {};
32
+ }
33
+ const editorRect = editor.view.dom.getBoundingClientRect();
34
+ elements.floating.style.setProperty(
35
+ "--lb-tiptap-editor-width",
36
+ `${editorRect.width}px`
37
+ );
38
+ elements.floating.style.setProperty(
39
+ "--lb-tiptap-editor-height",
40
+ `${editorRect.height}px`
41
+ );
42
+ return {
43
+ x: editorRect.x
44
+ };
45
+ }
46
+ };
47
+ }
48
+ const AiToolbarDropdownGroup = react.forwardRef(({ children, label, ...props }, forwardedRef) => {
49
+ return /* @__PURE__ */ jsxRuntime.jsx(cmdk.Command.Group, {
50
+ heading: /* @__PURE__ */ jsxRuntime.jsx("span", {
51
+ className: "lb-dropdown-label",
52
+ children: label
53
+ }),
54
+ ...props,
55
+ ref: forwardedRef,
56
+ children
57
+ });
58
+ });
59
+ const AiToolbarSuggestionsGroup = react.forwardRef((props, forwardedRef) => {
60
+ return /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownGroup, {
61
+ ref: forwardedRef,
62
+ ...props
63
+ });
64
+ });
65
+ const AiToolbarDropdownItem = react.forwardRef(({ children, onSelect, icon, ...props }, forwardedRef) => {
66
+ return /* @__PURE__ */ jsxRuntime.jsxs(cmdk.Command.Item, {
67
+ className: "lb-dropdown-item",
68
+ onSelect,
69
+ ...props,
70
+ ref: forwardedRef,
71
+ children: [
72
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("span", {
73
+ className: "lb-icon-container",
74
+ children: icon
75
+ }) : null,
76
+ children ? /* @__PURE__ */ jsxRuntime.jsx("span", {
77
+ className: "lb-dropdown-item-label",
78
+ children
79
+ }) : null
80
+ ]
81
+ });
82
+ });
83
+ const AiToolbarSuggestion = react.forwardRef(({ prompt: manualPrompt, ...props }, forwardedRef) => {
84
+ const editor = context.useCurrentEditor("Suggestion", "AiToolbar");
85
+ const handleSelect = react.useCallback(
86
+ (prompt) => {
87
+ editor.commands.$startAiToolbarThinking(
88
+ manualPrompt ?? prompt
89
+ );
90
+ },
91
+ [editor, manualPrompt]
92
+ );
93
+ return /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownItem, {
94
+ ...props,
95
+ onSelect: handleSelect,
96
+ ref: forwardedRef
97
+ });
98
+ });
99
+ function AiToolbarReviewingSuggestions() {
100
+ const editor = context.useCurrentEditor("ReviewingSuggestions", "AiToolbar");
101
+ const { state } = useAiToolbarContext();
102
+ const { prompt } = state;
103
+ const handleRetry = react.useCallback(() => {
104
+ editor.commands.$startAiToolbarThinking(prompt);
105
+ }, [editor, prompt]);
106
+ const handleDiscard = react.useCallback(() => {
107
+ editor.commands.$closeAiToolbar();
108
+ }, [editor]);
109
+ const handleAccept = react.useCallback(() => {
110
+ editor.commands.$acceptAiToolbarOutput();
111
+ }, [editor]);
112
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
113
+ children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownItem, {
115
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.CheckIcon, {}),
116
+ onSelect: handleAccept,
117
+ children: "Replace selection"
118
+ }),
119
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownItem, {
120
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.CheckIcon, {}),
121
+ children: "Insert below"
122
+ }),
123
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownItem, {
124
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.UndoIcon, {}),
125
+ onSelect: handleRetry,
126
+ children: "Try again"
127
+ }),
128
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarDropdownItem, {
129
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.CrossIcon, {}),
130
+ onSelect: handleDiscard,
131
+ children: "Discard"
132
+ })
133
+ ]
134
+ });
135
+ }
136
+ function AiToolbarCustomPromptContent() {
137
+ const editor = context.useCurrentEditor("CustomPromptContent", "AiToolbar");
138
+ const aiName = editor.storage.liveblocksAi.name;
139
+ const textAreaRef = react.useRef(null);
140
+ const { state, dropdownRef, isDropdownHidden } = useAiToolbarContext();
141
+ const { customPrompt } = state;
142
+ const isCustomPromptEmpty = react.useMemo(
143
+ () => customPrompt.trim() === "",
144
+ [customPrompt]
145
+ );
146
+ _private$1.useLayoutEffect(
147
+ () => {
148
+ setTimeout(() => {
149
+ const textArea = textAreaRef.current;
150
+ if (!textArea) {
151
+ return;
152
+ }
153
+ textArea.focus();
154
+ textArea.setSelectionRange(
155
+ textArea.value.length,
156
+ textArea.value.length
157
+ );
158
+ }, 0);
159
+ },
160
+ []
161
+ );
162
+ const handlePromptKeyDown = (event) => {
163
+ if (event.key === "Enter") {
164
+ event.preventDefault();
165
+ event.stopPropagation();
166
+ if (event.shiftKey) {
167
+ editor.commands._updateAiToolbarCustomPrompt(
168
+ (customPrompt2) => customPrompt2 + "\n"
169
+ );
170
+ } else {
171
+ const selectedDropdownItem = dropdownRef.current?.querySelector(
172
+ "[role='option'][data-selected='true']"
173
+ );
174
+ if (!isDropdownHidden && selectedDropdownItem) {
175
+ selectedDropdownItem.click();
176
+ } else if (!isCustomPromptEmpty) {
177
+ editor.commands.$startAiToolbarThinking(
178
+ customPrompt
179
+ );
180
+ }
181
+ }
182
+ }
183
+ };
184
+ const handleCustomPromptChange = react.useCallback(
185
+ (customPrompt2) => {
186
+ editor.commands._updateAiToolbarCustomPrompt(
187
+ customPrompt2
188
+ );
189
+ },
190
+ [editor]
191
+ );
192
+ const handleSendClick = react.useCallback(() => {
193
+ if (isCustomPromptEmpty) {
194
+ return;
195
+ }
196
+ editor.commands.$startAiToolbarThinking(
197
+ customPrompt
198
+ );
199
+ }, [editor, customPrompt, isCustomPromptEmpty]);
200
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", {
201
+ className: "lb-tiptap-ai-toolbar-content",
202
+ children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx("span", {
204
+ className: "lb-icon-container lb-tiptap-ai-toolbar-icon-container",
205
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.SparklesIcon, {})
206
+ }),
207
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
208
+ className: "lb-tiptap-ai-toolbar-custom-prompt-container",
209
+ "data-value": customPrompt,
210
+ children: /* @__PURE__ */ jsxRuntime.jsx(cmdk.Command.Input, {
211
+ value: customPrompt,
212
+ onValueChange: handleCustomPromptChange,
213
+ asChild: true,
214
+ children: /* @__PURE__ */ jsxRuntime.jsx("textarea", {
215
+ ref: textAreaRef,
216
+ className: "lb-tiptap-ai-toolbar-custom-prompt",
217
+ placeholder: `Ask ${aiName} anything\u2026`,
218
+ onKeyDown: handlePromptKeyDown,
219
+ rows: 1,
220
+ autoFocus: true
221
+ })
222
+ })
223
+ }),
224
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
225
+ className: "lb-tiptap-ai-toolbar-actions",
226
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.ShortcutTooltip, {
227
+ content: `Ask ${aiName}`,
228
+ shortcut: "Enter",
229
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.Button, {
230
+ className: "lb-tiptap-ai-toolbar-action",
231
+ variant: "primary",
232
+ "aria-label": `Ask ${aiName}`,
233
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.SendIcon, {}),
234
+ disabled: isCustomPromptEmpty,
235
+ onClick: handleSendClick
236
+ })
237
+ })
238
+ })
239
+ ]
240
+ });
241
+ }
242
+ function AiToolbarAsking() {
243
+ return /* @__PURE__ */ jsxRuntime.jsx(AiToolbarCustomPromptContent, {});
244
+ }
245
+ function AiToolbarThinking() {
246
+ const editor = context.useCurrentEditor("AiToolbarThinking", "AiToolbar");
247
+ const contentRef = react.useRef(null);
248
+ const aiName = editor.storage.liveblocksAi.name;
249
+ const handleCancel = react.useCallback(() => {
250
+ editor.commands.$cancelAiToolbarThinking();
251
+ }, [editor]);
252
+ _private$1.useLayoutEffect(() => {
253
+ contentRef.current?.focus();
254
+ window.getSelection()?.removeAllRanges();
255
+ }, []);
256
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {
257
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
258
+ className: "lb-tiptap-ai-toolbar-content",
259
+ tabIndex: 0,
260
+ ref: contentRef,
261
+ children: [
262
+ /* @__PURE__ */ jsxRuntime.jsx("span", {
263
+ className: "lb-icon-container lb-tiptap-ai-toolbar-icon-container",
264
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.SparklesIcon, {})
265
+ }),
266
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
267
+ className: "lb-tiptap-ai-toolbar-thinking",
268
+ children: [
269
+ aiName,
270
+ " is thinking\u2026"
271
+ ]
272
+ }),
273
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
274
+ className: "lb-tiptap-ai-toolbar-actions",
275
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.ShortcutTooltip, {
276
+ content: "Cancel",
277
+ shortcut: "Escape",
278
+ children: /* @__PURE__ */ jsxRuntime.jsx(_private.Button, {
279
+ className: "lb-tiptap-ai-toolbar-action",
280
+ variant: "secondary",
281
+ "aria-label": "Cancel",
282
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.UndoIcon, {}),
283
+ onClick: handleCancel
284
+ })
285
+ })
286
+ })
287
+ ]
288
+ })
289
+ });
290
+ }
291
+ function AiToolbarReviewing() {
292
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {
293
+ children: /* @__PURE__ */ jsxRuntime.jsx(AiToolbarCustomPromptContent, {})
294
+ });
295
+ }
296
+ function AiToolbarContainer({
297
+ state,
298
+ toolbarRef,
299
+ dropdownRef,
300
+ children
301
+ }) {
302
+ const editor = context.useCurrentEditor("AiToolbarContainer", "AiToolbar");
303
+ const phase = state.phase;
304
+ const customPrompt = state.customPrompt;
305
+ const isCustomPromptMultiline = react.useMemo(
306
+ () => customPrompt?.includes("\n"),
307
+ [customPrompt]
308
+ );
309
+ const hasDropdownItems = cmdk.useCommandState(
310
+ (state2) => state2.filtered.count > 0
311
+ );
312
+ const isDropdownHidden = isCustomPromptMultiline || !hasDropdownItems;
313
+ react.useEffect(() => {
314
+ if (!editor) {
315
+ return;
316
+ }
317
+ const handleKeyDown = (event) => {
318
+ if (!event.defaultPrevented && event.key === "Escape") {
319
+ event.preventDefault();
320
+ event.stopPropagation();
321
+ if (phase === "thinking") {
322
+ editor.commands.$cancelAiToolbarThinking();
323
+ } else {
324
+ editor.chain().$closeAiToolbar().focus().run();
325
+ }
326
+ }
327
+ };
328
+ document.addEventListener("keydown", handleKeyDown);
329
+ return () => {
330
+ document.removeEventListener("keydown", handleKeyDown);
331
+ };
332
+ }, [editor, phase]);
333
+ return /* @__PURE__ */ jsxRuntime.jsxs(AiToolbarContext.Provider, {
334
+ value: { state, toolbarRef, dropdownRef, isDropdownHidden },
335
+ children: [
336
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
337
+ className: "lb-tiptap-ai-toolbar-container",
338
+ children: [
339
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
340
+ className: "lb-elevation lb-tiptap-ai-toolbar",
341
+ children: state.phase === "asking" ? /* @__PURE__ */ jsxRuntime.jsx(AiToolbarAsking, {}) : state.phase === "thinking" ? /* @__PURE__ */ jsxRuntime.jsx(AiToolbarThinking, {}) : state.phase === "reviewing" ? /* @__PURE__ */ jsxRuntime.jsx(AiToolbarReviewing, {}) : null
342
+ }),
343
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
344
+ className: "lb-tiptap-ai-toolbar-halo",
345
+ "data-active": state.phase === "thinking" ? "" : void 0,
346
+ "aria-hidden": true,
347
+ children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
349
+ className: "lb-tiptap-ai-toolbar-halo-horizontal"
350
+ }),
351
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
352
+ className: "lb-tiptap-ai-toolbar-halo-vertical"
353
+ })
354
+ ]
355
+ })
356
+ ]
357
+ }),
358
+ state.phase === "asking" || state.phase === "reviewing" ? /* @__PURE__ */ jsxRuntime.jsx(cmdk.Command.List, {
359
+ className: "lb-elevation lb-dropdown lb-tiptap-ai-toolbar-dropdown",
360
+ "data-hidden": isDropdownHidden ? "" : void 0,
361
+ ref: dropdownRef,
362
+ children: state.phase === "reviewing" ? /* @__PURE__ */ jsxRuntime.jsx(AiToolbarReviewingSuggestions, {}) : children
363
+ }) : null
364
+ ]
365
+ });
366
+ }
367
+ const defaultSuggestions = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
368
+ children: [
369
+ /* @__PURE__ */ jsxRuntime.jsxs(AiToolbarSuggestionsGroup, {
370
+ label: "Modify",
371
+ children: [
372
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestion, {
373
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.EditIcon, {}),
374
+ children: "Improve writing"
375
+ }),
376
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestion, {
377
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.CheckIcon, {}),
378
+ children: "Fix mistakes"
379
+ }),
380
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestion, {
381
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.ShortenIcon, {}),
382
+ children: "Simplify"
383
+ }),
384
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestion, {
385
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.LengthenIcon, {}),
386
+ children: "Add more detail"
387
+ })
388
+ ]
389
+ }),
390
+ /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestionsGroup, {
391
+ label: "Generate",
392
+ children: /* @__PURE__ */ jsxRuntime.jsx(AiToolbarSuggestion, {
393
+ icon: /* @__PURE__ */ jsxRuntime.jsx(_private.QuestionMarkIcon, {}),
394
+ children: "Explain"
395
+ })
396
+ })
397
+ ]
398
+ });
399
+ const AiToolbar = Object.assign(
400
+ react.forwardRef(
401
+ ({
402
+ position = "bottom",
403
+ offset: sideOffset = 6,
404
+ editor,
405
+ className,
406
+ suggestions: Suggestions = defaultSuggestions,
407
+ ...props
408
+ }, forwardedRef) => {
409
+ const state = react$1.useEditorState({
410
+ editor,
411
+ selector: (ctx) => {
412
+ return ctx.editor?.storage.liveblocksAi?.state;
413
+ }
414
+ }) ?? AiExtension.DEFAULT_STATE;
415
+ const selection = editor?.state.selection;
416
+ const phase = state.phase;
417
+ const floatingOptions = react.useMemo(() => {
418
+ const detectOverflowOptions = {
419
+ padding: AI_TOOLBAR_COLLISION_PADDING
420
+ };
421
+ return {
422
+ strategy: "fixed",
423
+ placement: position,
424
+ middleware: [
425
+ tiptapFloating(editor),
426
+ reactDom.hide(detectOverflowOptions),
427
+ reactDom.offset(sideOffset)
428
+ ],
429
+ whileElementsMounted: (...args) => {
430
+ return reactDom.autoUpdate(...args, {
431
+ animationFrame: true
432
+ });
433
+ }
434
+ };
435
+ }, [editor, position, sideOffset]);
436
+ const isOpen = selection !== void 0 && state.phase !== "closed";
437
+ const {
438
+ refs: { setReference, setFloating },
439
+ strategy,
440
+ x,
441
+ y,
442
+ isPositioned
443
+ } = reactDom.useFloating({
444
+ ...floatingOptions,
445
+ open: isOpen
446
+ });
447
+ const toolbarRef = react.useRef(null);
448
+ const mergedRefs = _private.useRefs(forwardedRef, toolbarRef, setFloating);
449
+ const dropdownRef = react.useRef(null);
450
+ react.useEffect(() => {
451
+ if (!editor) {
452
+ return;
453
+ }
454
+ if (!selection && phase !== "closed") {
455
+ editor.commands.$closeAiToolbar();
456
+ }
457
+ }, [phase, editor, selection]);
458
+ _private$1.useLayoutEffect(() => {
459
+ if (!editor || !isOpen) {
460
+ return;
461
+ }
462
+ setReference(null);
463
+ setTimeout(() => {
464
+ if (state.phase === "reviewing") {
465
+ const domRange = utils.getDomRangeFromSelection(state.contentTarget, editor);
466
+ setReference(domRange);
467
+ } else if (!selection) {
468
+ setReference(null);
469
+ } else {
470
+ const domRange = utils.getDomRangeFromSelection(selection, editor);
471
+ setReference(domRange);
472
+ }
473
+ }, 0);
474
+ }, [selection, editor, isOpen, state.phase, state.contentTarget, setReference]);
475
+ react.useEffect(() => {
476
+ if (!editor || !isOpen) {
477
+ return;
478
+ }
479
+ const handleOutsideEvent = (event) => {
480
+ if (!toolbarRef.current) {
481
+ return;
482
+ }
483
+ if (event.target && !toolbarRef.current.contains(event.target) && (dropdownRef.current ? !dropdownRef.current.contains(event.target) : true)) {
484
+ editor.commands.$closeAiToolbar();
485
+ }
486
+ };
487
+ setTimeout(() => {
488
+ document.addEventListener("pointerdown", handleOutsideEvent);
489
+ }, 0);
490
+ return () => {
491
+ document.removeEventListener("pointerdown", handleOutsideEvent);
492
+ };
493
+ }, [editor, isOpen]);
494
+ if (!editor || !isOpen) {
495
+ return null;
496
+ }
497
+ return reactDom$1.createPortal(
498
+ /* @__PURE__ */ jsxRuntime.jsx(_private.TooltipProvider, {
499
+ children: /* @__PURE__ */ jsxRuntime.jsx(context.EditorProvider, {
500
+ editor,
501
+ children: /* @__PURE__ */ jsxRuntime.jsx(cmdk.Command, {
502
+ role: "toolbar",
503
+ label: "AI toolbar",
504
+ "aria-orientation": "horizontal",
505
+ className: classnames.classNames(
506
+ "lb-root lb-portal lb-tiptap-ai-toolbar-portal",
507
+ className
508
+ ),
509
+ ref: mergedRefs,
510
+ style: {
511
+ position: strategy,
512
+ top: 0,
513
+ left: 0,
514
+ transform: isPositioned ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)` : "translate3d(0, -200%, 0)"
515
+ },
516
+ ...props,
517
+ children: /* @__PURE__ */ jsxRuntime.jsx(AiToolbarContainer, {
518
+ state,
519
+ dropdownRef,
520
+ toolbarRef,
521
+ children: typeof Suggestions === "function" ? /* @__PURE__ */ jsxRuntime.jsx(Suggestions, {
522
+ children: defaultSuggestions
523
+ }) : Suggestions
524
+ })
525
+ })
526
+ })
527
+ }),
528
+ document.body
529
+ );
530
+ }
531
+ ),
532
+ {
533
+ SuggestionsGroup: AiToolbarSuggestionsGroup,
534
+ Suggestion: AiToolbarSuggestion
535
+ }
536
+ );
537
+
538
+ exports.AI_TOOLBAR_COLLISION_PADDING = AI_TOOLBAR_COLLISION_PADDING;
539
+ exports.AiToolbar = AiToolbar;
540
+ //# sourceMappingURL=AiToolbar.js.map