@liveblocks/react-tiptap 2.16.0 → 2.16.1-ai1

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 (42) hide show
  1. package/dist/LiveblocksExtension.js +120 -21
  2. package/dist/LiveblocksExtension.js.map +1 -1
  3. package/dist/LiveblocksExtension.mjs +118 -19
  4. package/dist/LiveblocksExtension.mjs.map +1 -1
  5. package/dist/ai/AiExtension.js +299 -0
  6. package/dist/ai/AiExtension.js.map +1 -0
  7. package/dist/ai/AiExtension.mjs +296 -0
  8. package/dist/ai/AiExtension.mjs.map +1 -0
  9. package/dist/ai/AiToolbar.js +570 -0
  10. package/dist/ai/AiToolbar.js.map +1 -0
  11. package/dist/ai/AiToolbar.mjs +567 -0
  12. package/dist/ai/AiToolbar.mjs.map +1 -0
  13. package/dist/comments/CommentsExtension.js.map +1 -1
  14. package/dist/comments/CommentsExtension.mjs.map +1 -1
  15. package/dist/index.d.mts +58 -16
  16. package/dist/index.d.ts +58 -16
  17. package/dist/index.js +2 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +1 -0
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/toolbar/FloatingToolbar.js +7 -0
  22. package/dist/toolbar/FloatingToolbar.js.map +1 -1
  23. package/dist/toolbar/FloatingToolbar.mjs +7 -0
  24. package/dist/toolbar/FloatingToolbar.mjs.map +1 -1
  25. package/dist/toolbar/Toolbar.js +37 -2
  26. package/dist/toolbar/Toolbar.js.map +1 -1
  27. package/dist/toolbar/Toolbar.mjs +38 -3
  28. package/dist/toolbar/Toolbar.mjs.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/dist/types.mjs.map +1 -1
  31. package/dist/utils.js +21 -1
  32. package/dist/utils.js.map +1 -1
  33. package/dist/utils.mjs +20 -2
  34. package/dist/utils.mjs.map +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/version.js.map +1 -1
  37. package/dist/version.mjs +1 -1
  38. package/dist/version.mjs.map +1 -1
  39. package/package.json +7 -6
  40. package/src/styles/index.css +319 -3
  41. package/styles.css +1 -1
  42. package/styles.css.map +1 -1
@@ -0,0 +1,299 @@
1
+ 'use strict';
2
+
3
+ var core$1 = require('@liveblocks/core');
4
+ var core = require('@tiptap/core');
5
+ var model = require('@tiptap/pm/model');
6
+ var state = require('@tiptap/pm/state');
7
+ var view = require('@tiptap/pm/view');
8
+ var yProsemirror = require('y-prosemirror');
9
+ var yjs = require('yjs');
10
+ var types = require('../types.js');
11
+
12
+ const DEFAULT_AI_NAME = "AI";
13
+ const DEFAULT_STATE = { phase: "closed" };
14
+ const RESOLVE_AI_PROMPT_RETRY_ATTEMPTS = 3;
15
+ const RESOLVE_AI_PROMPT_RETRY_DELAYS = [1e3, 2e3, 4e3];
16
+ function getYjsBinding(editor) {
17
+ return yProsemirror.ySyncPluginKey.getState(editor.view.state).binding;
18
+ }
19
+ function getLiveblocksYjsProvider(editor) {
20
+ return editor.extensionStorage.liveblocksExtension?.provider;
21
+ }
22
+ const AiExtension = core.Extension.create({
23
+ name: "liveblocksAi",
24
+ addOptions() {
25
+ return {
26
+ doc: void 0,
27
+ pud: void 0,
28
+ resolveAiPrompt: () => Promise.reject(),
29
+ name: DEFAULT_AI_NAME
30
+ };
31
+ },
32
+ addStorage() {
33
+ return {
34
+ state: DEFAULT_STATE,
35
+ name: this.options.name
36
+ };
37
+ },
38
+ addCommands() {
39
+ return {
40
+ askAi: (prompt) => () => {
41
+ if (typeof prompt === "string") {
42
+ this.editor.commands.$startAiToolbarThinking(prompt);
43
+ } else {
44
+ this.editor.commands.$openAiToolbarAsking();
45
+ }
46
+ return true;
47
+ },
48
+ $acceptAiToolbarOutput: () => ({ tr }) => {
49
+ const currentState = this.storage.state;
50
+ if (currentState.phase !== "reviewing") {
51
+ return false;
52
+ }
53
+ const binding = getYjsBinding(this.editor);
54
+ if (!binding) {
55
+ return false;
56
+ }
57
+ const fragmentContent = yProsemirror.yXmlFragmentToProseMirrorFragment(
58
+ binding.type,
59
+ this.editor.state.schema
60
+ );
61
+ tr.setMeta("addToHistory", false);
62
+ tr.replace(
63
+ 0,
64
+ this.editor.state.doc.content.size,
65
+ new model.Slice(model.Fragment.from(fragmentContent), 0, 0)
66
+ );
67
+ tr.setMeta(yProsemirror.ySyncPluginKey, {
68
+ snapshot: null,
69
+ prevSnapshot: null
70
+ });
71
+ getLiveblocksYjsProvider(this.editor)?.unpause();
72
+ this.editor.setEditable(true);
73
+ this.storage.snapshot = void 0;
74
+ this.storage.state = { phase: "closed" };
75
+ return true;
76
+ },
77
+ $closeAiToolbar: () => ({ tr }) => {
78
+ const currentState = this.storage.state;
79
+ if (currentState.phase === "thinking") {
80
+ currentState.abortController.abort();
81
+ }
82
+ if (currentState.phase === "thinking" || currentState.phase === "reviewing") {
83
+ if (this.storage.snapshot) {
84
+ const binding = getYjsBinding(this.editor);
85
+ if (binding) {
86
+ binding.mapping.clear();
87
+ const docFromSnapshot = yjs.createDocFromSnapshot(
88
+ binding.doc,
89
+ this.storage.snapshot
90
+ );
91
+ const type = docFromSnapshot.getXmlFragment("default");
92
+ const fragmentContent = yProsemirror.yXmlFragmentToProseMirrorFragment(
93
+ type,
94
+ this.editor.state.schema
95
+ );
96
+ tr.setMeta("addToHistory", false);
97
+ tr.replace(
98
+ 0,
99
+ this.editor.state.doc.content.size,
100
+ new model.Slice(model.Fragment.from(fragmentContent), 0, 0)
101
+ );
102
+ tr.setMeta(yProsemirror.ySyncPluginKey, {
103
+ snapshot: null,
104
+ prevSnapshot: null
105
+ });
106
+ getLiveblocksYjsProvider(this.editor)?.unpause();
107
+ if (this.options.doc) {
108
+ this.options.doc.gc = true;
109
+ }
110
+ this.storage.snapshot = void 0;
111
+ }
112
+ }
113
+ this.editor.setEditable(true);
114
+ }
115
+ this.storage.state = { phase: "closed" };
116
+ return true;
117
+ },
118
+ $openAiToolbarAsking: () => () => {
119
+ const currentState = this.storage.state;
120
+ if (currentState.phase !== "closed") {
121
+ return false;
122
+ }
123
+ if (this.editor.isFocused) {
124
+ this.editor.commands.blur();
125
+ }
126
+ this.storage.state = {
127
+ phase: "asking",
128
+ customPrompt: ""
129
+ };
130
+ return true;
131
+ },
132
+ $startAiToolbarThinking: (prompt) => () => {
133
+ const currentState = this.storage.state;
134
+ if (currentState.phase === "thinking") {
135
+ return false;
136
+ }
137
+ if (currentState.phase === "reviewing") {
138
+ return this.editor.commands.$closeAiToolbar();
139
+ }
140
+ if (this.editor.isFocused) {
141
+ this.editor.commands.blur();
142
+ }
143
+ const abortController = new AbortController();
144
+ const provider = getLiveblocksYjsProvider(this.editor);
145
+ this.storage.state = {
146
+ phase: "thinking",
147
+ customPrompt: currentState.customPrompt ?? "",
148
+ prompt,
149
+ abortController
150
+ };
151
+ this.editor.setEditable(false);
152
+ core$1.autoRetry(
153
+ async () => {
154
+ await provider?.pause();
155
+ const { from, to } = this.editor.state.selection.empty ? {
156
+ from: Math.max(this.editor.state.selection.to - 30, 0),
157
+ to: this.editor.state.selection.to
158
+ } : this.editor.state.selection;
159
+ return this.options.resolveAiPrompt({
160
+ prompt,
161
+ selectionText: this.editor.state.doc.textBetween(from, to, " "),
162
+ context: this.editor.getText().slice(0, 3e3),
163
+ signal: abortController.signal
164
+ });
165
+ },
166
+ RESOLVE_AI_PROMPT_RETRY_ATTEMPTS,
167
+ RESOLVE_AI_PROMPT_RETRY_DELAYS,
168
+ (error) => {
169
+ return abortController.signal.aborted || error instanceof core$1.HttpError && error.status >= 400 && error.status < 500;
170
+ }
171
+ ).then((output) => {
172
+ if (abortController.signal.aborted) {
173
+ return;
174
+ }
175
+ this.editor.commands._handleAiToolbarThinkingSuccess({
176
+ type: output.type,
177
+ text: output.content
178
+ });
179
+ }).catch((error) => {
180
+ if (abortController.signal.aborted) {
181
+ return;
182
+ }
183
+ this.editor.commands._handleAiToolbarThinkingError(error);
184
+ });
185
+ return true;
186
+ },
187
+ $cancelAiToolbarThinking: () => () => {
188
+ const currentState = this.storage.state;
189
+ if (currentState.phase !== "thinking") {
190
+ return false;
191
+ }
192
+ currentState.abortController.abort();
193
+ this.editor.setEditable(true);
194
+ this.storage.state = {
195
+ phase: "asking",
196
+ customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : ""
197
+ };
198
+ return true;
199
+ },
200
+ _handleAiToolbarThinkingSuccess: (output) => () => {
201
+ const currentState = this.storage.state;
202
+ if (currentState.phase !== "thinking" || !this.options.doc) {
203
+ return false;
204
+ }
205
+ let selection = this.editor.state.selection;
206
+ if (["modification", "insert"].includes(output.type)) {
207
+ this.options.doc.gc = false;
208
+ this.storage.snapshot = yjs.snapshot(this.options.doc);
209
+ const { empty, from, to } = this.editor.state.selection;
210
+ const contentTarget = empty || output.type === "insert" ? to : {
211
+ from,
212
+ to
213
+ };
214
+ const targetTo = output.type === "insert" ? to : from;
215
+ this.editor.commands.insertContentAt(contentTarget, output.text);
216
+ setTimeout(() => {
217
+ if (this.storage.snapshot) {
218
+ this.editor.commands._renderAiToolbarDiffInEditor(this.storage.snapshot);
219
+ }
220
+ }, 100);
221
+ selection = {
222
+ from,
223
+ to: targetTo + output.text.length
224
+ };
225
+ }
226
+ this.storage.state = {
227
+ phase: "reviewing",
228
+ customPrompt: "",
229
+ prompt: currentState.prompt,
230
+ output,
231
+ selection
232
+ };
233
+ return true;
234
+ },
235
+ _handleAiToolbarThinkingError: (error) => () => {
236
+ const currentState = this.storage.state;
237
+ if (currentState.phase !== "thinking") {
238
+ return false;
239
+ }
240
+ this.editor.setEditable(true);
241
+ this.storage.state = {
242
+ phase: "asking",
243
+ customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : "",
244
+ error
245
+ };
246
+ return true;
247
+ },
248
+ _updateAiToolbarCustomPrompt: (customPrompt) => () => {
249
+ const currentState = this.storage.state;
250
+ if (typeof currentState.customPrompt !== "string") {
251
+ return false;
252
+ }
253
+ this.storage.state.customPrompt = typeof customPrompt === "function" ? customPrompt(currentState.customPrompt) : customPrompt;
254
+ return true;
255
+ },
256
+ _renderAiToolbarDiffInEditor: (previous) => () => {
257
+ if (!this.options.doc) {
258
+ return false;
259
+ }
260
+ const previousSnapshot = previous ?? yjs.emptySnapshot;
261
+ const currentSnapshot = yjs.snapshot(this.options.doc);
262
+ if (yjs.equalSnapshots(previousSnapshot, currentSnapshot)) {
263
+ return true;
264
+ }
265
+ const binding = getYjsBinding(this.editor);
266
+ if (binding) {
267
+ binding.renderSnapshot(currentSnapshot, previousSnapshot);
268
+ return true;
269
+ }
270
+ return false;
271
+ }
272
+ };
273
+ },
274
+ addProseMirrorPlugins() {
275
+ return [
276
+ new state.Plugin({
277
+ key: types.AI_TOOLBAR_SELECTION_PLUGIN,
278
+ props: {
279
+ decorations: ({ doc, selection }) => {
280
+ if (this.storage.state.phase === "closed" || this.storage.state.phase === "reviewing" && this.storage.state.output.type !== "other") {
281
+ return view.DecorationSet.create(doc, []);
282
+ }
283
+ const { from, to } = this.storage.state.selection ?? selection;
284
+ const decorations = [
285
+ view.Decoration.inline(from, to, {
286
+ class: "lb-root lb-selection lb-tiptap-active-selection"
287
+ })
288
+ ];
289
+ return view.DecorationSet.create(doc, decorations);
290
+ }
291
+ }
292
+ })
293
+ ];
294
+ }
295
+ });
296
+
297
+ exports.AiExtension = AiExtension;
298
+ exports.DEFAULT_STATE = DEFAULT_STATE;
299
+ //# sourceMappingURL=AiExtension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiExtension.js","sources":["../../src/ai/AiExtension.ts"],"sourcesContent":["import { autoRetry, HttpError } from \"@liveblocks/core\";\nimport type { LiveblocksYjsProvider } from \"@liveblocks/yjs\";\nimport type { CommandProps, Editor } from \"@tiptap/core\";\nimport { Extension } from \"@tiptap/core\";\nimport { Fragment, Slice } from \"@tiptap/pm/model\";\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\nconst RESOLVE_AI_PROMPT_RETRY_ATTEMPTS = 3;\nconst RESOLVE_AI_PROMPT_RETRY_DELAYS = [1000, 2000, 4000];\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 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 ({ tr }: CommandProps) => {\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 ({ tr }: CommandProps) => {\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 // Maybe not, it could be a refinement prompt\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. Start the AI request\n autoRetry(\n 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\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 RESOLVE_AI_PROMPT_RETRY_ATTEMPTS,\n RESOLVE_AI_PROMPT_RETRY_DELAYS,\n\n (error) => {\n // Don't retry on 4xx errors or if the request was aborted\n return (\n abortController.signal.aborted ||\n (error instanceof HttpError &&\n error.status >= 400 &&\n error.status < 500)\n );\n }\n )\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 let selection: { from: number; to: number } =\n this.editor.state.selection;\n\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\n const { empty, from, to } = this.editor.state.selection;\n\n // if the selection is empty, insert at the end of the selection\n const contentTarget =\n empty || output.type === \"insert\"\n ? 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 // 2.a. Insert the output.\n this.editor.commands.insertContentAt(contentTarget, output.text);\n\n // 2.b. Diff the output in the editor\n // TODO: 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\n selection = {\n from,\n to: targetTo + output.text.length, // take into account the new length with output\n };\n }\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 selection,\n };\n\n return true;\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 (\n this.storage.state.phase === \"closed\" ||\n // TODO: Don't show the selection when reviewing diff outputs\n (this.storage.state.phase === \"reviewing\" &&\n this.storage.state.output.type !== \"other\")\n ) {\n return DecorationSet.create(doc, []);\n }\n const { from, to } = this.storage.state.selection ?? 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":["ySyncPluginKey","Extension","yXmlFragmentToProseMirrorFragment","Slice","Fragment","createDocFromSnapshot","autoRetry","HttpError","snapshot","emptySnapshot","equalSnapshots","Plugin","AI_TOOLBAR_SELECTION_PLUGIN","DecorationSet","Decoration"],"mappings":"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"}
@@ -0,0 +1,296 @@
1
+ import { autoRetry, HttpError } from '@liveblocks/core';
2
+ import { Extension } from '@tiptap/core';
3
+ import { Slice, Fragment } from '@tiptap/pm/model';
4
+ import { Plugin } from '@tiptap/pm/state';
5
+ import { DecorationSet, Decoration } from '@tiptap/pm/view';
6
+ import { ySyncPluginKey, yXmlFragmentToProseMirrorFragment } from 'y-prosemirror';
7
+ import { createDocFromSnapshot, snapshot, emptySnapshot, equalSnapshots } from 'yjs';
8
+ import { AI_TOOLBAR_SELECTION_PLUGIN } from '../types.mjs';
9
+
10
+ const DEFAULT_AI_NAME = "AI";
11
+ const DEFAULT_STATE = { phase: "closed" };
12
+ const RESOLVE_AI_PROMPT_RETRY_ATTEMPTS = 3;
13
+ const RESOLVE_AI_PROMPT_RETRY_DELAYS = [1e3, 2e3, 4e3];
14
+ function getYjsBinding(editor) {
15
+ return ySyncPluginKey.getState(editor.view.state).binding;
16
+ }
17
+ function getLiveblocksYjsProvider(editor) {
18
+ return editor.extensionStorage.liveblocksExtension?.provider;
19
+ }
20
+ const AiExtension = Extension.create({
21
+ name: "liveblocksAi",
22
+ addOptions() {
23
+ return {
24
+ doc: void 0,
25
+ pud: void 0,
26
+ resolveAiPrompt: () => Promise.reject(),
27
+ name: DEFAULT_AI_NAME
28
+ };
29
+ },
30
+ addStorage() {
31
+ return {
32
+ state: DEFAULT_STATE,
33
+ name: this.options.name
34
+ };
35
+ },
36
+ addCommands() {
37
+ return {
38
+ askAi: (prompt) => () => {
39
+ if (typeof prompt === "string") {
40
+ this.editor.commands.$startAiToolbarThinking(prompt);
41
+ } else {
42
+ this.editor.commands.$openAiToolbarAsking();
43
+ }
44
+ return true;
45
+ },
46
+ $acceptAiToolbarOutput: () => ({ tr }) => {
47
+ const currentState = this.storage.state;
48
+ if (currentState.phase !== "reviewing") {
49
+ return false;
50
+ }
51
+ const binding = getYjsBinding(this.editor);
52
+ if (!binding) {
53
+ return false;
54
+ }
55
+ const fragmentContent = yXmlFragmentToProseMirrorFragment(
56
+ binding.type,
57
+ this.editor.state.schema
58
+ );
59
+ tr.setMeta("addToHistory", false);
60
+ tr.replace(
61
+ 0,
62
+ this.editor.state.doc.content.size,
63
+ new Slice(Fragment.from(fragmentContent), 0, 0)
64
+ );
65
+ tr.setMeta(ySyncPluginKey, {
66
+ snapshot: null,
67
+ prevSnapshot: null
68
+ });
69
+ getLiveblocksYjsProvider(this.editor)?.unpause();
70
+ this.editor.setEditable(true);
71
+ this.storage.snapshot = void 0;
72
+ this.storage.state = { phase: "closed" };
73
+ return true;
74
+ },
75
+ $closeAiToolbar: () => ({ tr }) => {
76
+ const currentState = this.storage.state;
77
+ if (currentState.phase === "thinking") {
78
+ currentState.abortController.abort();
79
+ }
80
+ if (currentState.phase === "thinking" || currentState.phase === "reviewing") {
81
+ if (this.storage.snapshot) {
82
+ const binding = getYjsBinding(this.editor);
83
+ if (binding) {
84
+ binding.mapping.clear();
85
+ const docFromSnapshot = createDocFromSnapshot(
86
+ binding.doc,
87
+ this.storage.snapshot
88
+ );
89
+ const type = docFromSnapshot.getXmlFragment("default");
90
+ const fragmentContent = yXmlFragmentToProseMirrorFragment(
91
+ type,
92
+ this.editor.state.schema
93
+ );
94
+ tr.setMeta("addToHistory", false);
95
+ tr.replace(
96
+ 0,
97
+ this.editor.state.doc.content.size,
98
+ new Slice(Fragment.from(fragmentContent), 0, 0)
99
+ );
100
+ tr.setMeta(ySyncPluginKey, {
101
+ snapshot: null,
102
+ prevSnapshot: null
103
+ });
104
+ getLiveblocksYjsProvider(this.editor)?.unpause();
105
+ if (this.options.doc) {
106
+ this.options.doc.gc = true;
107
+ }
108
+ this.storage.snapshot = void 0;
109
+ }
110
+ }
111
+ this.editor.setEditable(true);
112
+ }
113
+ this.storage.state = { phase: "closed" };
114
+ return true;
115
+ },
116
+ $openAiToolbarAsking: () => () => {
117
+ const currentState = this.storage.state;
118
+ if (currentState.phase !== "closed") {
119
+ return false;
120
+ }
121
+ if (this.editor.isFocused) {
122
+ this.editor.commands.blur();
123
+ }
124
+ this.storage.state = {
125
+ phase: "asking",
126
+ customPrompt: ""
127
+ };
128
+ return true;
129
+ },
130
+ $startAiToolbarThinking: (prompt) => () => {
131
+ const currentState = this.storage.state;
132
+ if (currentState.phase === "thinking") {
133
+ return false;
134
+ }
135
+ if (currentState.phase === "reviewing") {
136
+ return this.editor.commands.$closeAiToolbar();
137
+ }
138
+ if (this.editor.isFocused) {
139
+ this.editor.commands.blur();
140
+ }
141
+ const abortController = new AbortController();
142
+ const provider = getLiveblocksYjsProvider(this.editor);
143
+ this.storage.state = {
144
+ phase: "thinking",
145
+ customPrompt: currentState.customPrompt ?? "",
146
+ prompt,
147
+ abortController
148
+ };
149
+ this.editor.setEditable(false);
150
+ autoRetry(
151
+ async () => {
152
+ await provider?.pause();
153
+ const { from, to } = this.editor.state.selection.empty ? {
154
+ from: Math.max(this.editor.state.selection.to - 30, 0),
155
+ to: this.editor.state.selection.to
156
+ } : this.editor.state.selection;
157
+ return this.options.resolveAiPrompt({
158
+ prompt,
159
+ selectionText: this.editor.state.doc.textBetween(from, to, " "),
160
+ context: this.editor.getText().slice(0, 3e3),
161
+ signal: abortController.signal
162
+ });
163
+ },
164
+ RESOLVE_AI_PROMPT_RETRY_ATTEMPTS,
165
+ RESOLVE_AI_PROMPT_RETRY_DELAYS,
166
+ (error) => {
167
+ return abortController.signal.aborted || error instanceof HttpError && error.status >= 400 && error.status < 500;
168
+ }
169
+ ).then((output) => {
170
+ if (abortController.signal.aborted) {
171
+ return;
172
+ }
173
+ this.editor.commands._handleAiToolbarThinkingSuccess({
174
+ type: output.type,
175
+ text: output.content
176
+ });
177
+ }).catch((error) => {
178
+ if (abortController.signal.aborted) {
179
+ return;
180
+ }
181
+ this.editor.commands._handleAiToolbarThinkingError(error);
182
+ });
183
+ return true;
184
+ },
185
+ $cancelAiToolbarThinking: () => () => {
186
+ const currentState = this.storage.state;
187
+ if (currentState.phase !== "thinking") {
188
+ return false;
189
+ }
190
+ currentState.abortController.abort();
191
+ this.editor.setEditable(true);
192
+ this.storage.state = {
193
+ phase: "asking",
194
+ customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : ""
195
+ };
196
+ return true;
197
+ },
198
+ _handleAiToolbarThinkingSuccess: (output) => () => {
199
+ const currentState = this.storage.state;
200
+ if (currentState.phase !== "thinking" || !this.options.doc) {
201
+ return false;
202
+ }
203
+ let selection = this.editor.state.selection;
204
+ if (["modification", "insert"].includes(output.type)) {
205
+ this.options.doc.gc = false;
206
+ this.storage.snapshot = snapshot(this.options.doc);
207
+ const { empty, from, to } = this.editor.state.selection;
208
+ const contentTarget = empty || output.type === "insert" ? to : {
209
+ from,
210
+ to
211
+ };
212
+ const targetTo = output.type === "insert" ? to : from;
213
+ this.editor.commands.insertContentAt(contentTarget, output.text);
214
+ setTimeout(() => {
215
+ if (this.storage.snapshot) {
216
+ this.editor.commands._renderAiToolbarDiffInEditor(this.storage.snapshot);
217
+ }
218
+ }, 100);
219
+ selection = {
220
+ from,
221
+ to: targetTo + output.text.length
222
+ };
223
+ }
224
+ this.storage.state = {
225
+ phase: "reviewing",
226
+ customPrompt: "",
227
+ prompt: currentState.prompt,
228
+ output,
229
+ selection
230
+ };
231
+ return true;
232
+ },
233
+ _handleAiToolbarThinkingError: (error) => () => {
234
+ const currentState = this.storage.state;
235
+ if (currentState.phase !== "thinking") {
236
+ return false;
237
+ }
238
+ this.editor.setEditable(true);
239
+ this.storage.state = {
240
+ phase: "asking",
241
+ customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : "",
242
+ error
243
+ };
244
+ return true;
245
+ },
246
+ _updateAiToolbarCustomPrompt: (customPrompt) => () => {
247
+ const currentState = this.storage.state;
248
+ if (typeof currentState.customPrompt !== "string") {
249
+ return false;
250
+ }
251
+ this.storage.state.customPrompt = typeof customPrompt === "function" ? customPrompt(currentState.customPrompt) : customPrompt;
252
+ return true;
253
+ },
254
+ _renderAiToolbarDiffInEditor: (previous) => () => {
255
+ if (!this.options.doc) {
256
+ return false;
257
+ }
258
+ const previousSnapshot = previous ?? emptySnapshot;
259
+ const currentSnapshot = snapshot(this.options.doc);
260
+ if (equalSnapshots(previousSnapshot, currentSnapshot)) {
261
+ return true;
262
+ }
263
+ const binding = getYjsBinding(this.editor);
264
+ if (binding) {
265
+ binding.renderSnapshot(currentSnapshot, previousSnapshot);
266
+ return true;
267
+ }
268
+ return false;
269
+ }
270
+ };
271
+ },
272
+ addProseMirrorPlugins() {
273
+ return [
274
+ new Plugin({
275
+ key: AI_TOOLBAR_SELECTION_PLUGIN,
276
+ props: {
277
+ decorations: ({ doc, selection }) => {
278
+ if (this.storage.state.phase === "closed" || this.storage.state.phase === "reviewing" && this.storage.state.output.type !== "other") {
279
+ return DecorationSet.create(doc, []);
280
+ }
281
+ const { from, to } = this.storage.state.selection ?? selection;
282
+ const decorations = [
283
+ Decoration.inline(from, to, {
284
+ class: "lb-root lb-selection lb-tiptap-active-selection"
285
+ })
286
+ ];
287
+ return DecorationSet.create(doc, decorations);
288
+ }
289
+ }
290
+ })
291
+ ];
292
+ }
293
+ });
294
+
295
+ export { AiExtension, DEFAULT_STATE };
296
+ //# sourceMappingURL=AiExtension.mjs.map