@liveblocks/react-tiptap 2.16.1-ai2 → 2.16.1-ai4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LiveblocksExtension.js +7 -11
- package/dist/LiveblocksExtension.js.map +1 -1
- package/dist/LiveblocksExtension.mjs +7 -11
- package/dist/LiveblocksExtension.mjs.map +1 -1
- package/dist/ai/AiExtension.js +186 -109
- package/dist/ai/AiExtension.js.map +1 -1
- package/dist/ai/AiExtension.mjs +187 -111
- package/dist/ai/AiExtension.mjs.map +1 -1
- package/dist/ai/AiToolbar.js +182 -89
- package/dist/ai/AiToolbar.js.map +1 -1
- package/dist/ai/AiToolbar.mjs +186 -93
- package/dist/ai/AiToolbar.mjs.map +1 -1
- package/dist/comments/FloatingComposer.js +2 -2
- package/dist/comments/FloatingComposer.js.map +1 -1
- package/dist/comments/FloatingComposer.mjs +2 -2
- package/dist/comments/FloatingComposer.mjs.map +1 -1
- package/dist/index.d.mts +57 -16
- package/dist/index.d.ts +57 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/toolbar/Toolbar.js +1 -1
- package/dist/toolbar/Toolbar.js.map +1 -1
- package/dist/toolbar/Toolbar.mjs +1 -1
- package/dist/toolbar/Toolbar.mjs.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/dist/utils.js +98 -6
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +98 -7
- package/dist/utils.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +6 -6
- package/src/styles/index.css +115 -59
- package/src/styles/utils.css +6 -0
- package/styles.css +1 -1
- package/styles.css.map +1 -1
package/dist/ai/AiExtension.mjs
CHANGED
|
@@ -4,8 +4,9 @@ import { Slice, Fragment } from '@tiptap/pm/model';
|
|
|
4
4
|
import { Plugin } from '@tiptap/pm/state';
|
|
5
5
|
import { DecorationSet, Decoration } from '@tiptap/pm/view';
|
|
6
6
|
import { ySyncPluginKey, yXmlFragmentToProseMirrorFragment } from 'y-prosemirror';
|
|
7
|
-
import { createDocFromSnapshot,
|
|
7
|
+
import { createDocFromSnapshot, emptySnapshot, snapshot, equalSnapshots } from 'yjs';
|
|
8
8
|
import { AI_TOOLBAR_SELECTION_PLUGIN } from '../types.mjs';
|
|
9
|
+
import { getContextualPromptContext } from '../utils.mjs';
|
|
9
10
|
|
|
10
11
|
const DEFAULT_AI_NAME = "AI";
|
|
11
12
|
const DEFAULT_STATE = { phase: "closed" };
|
|
@@ -17,13 +18,52 @@ function getYjsBinding(editor) {
|
|
|
17
18
|
function getLiveblocksYjsProvider(editor) {
|
|
18
19
|
return editor.extensionStorage.liveblocksExtension?.provider;
|
|
19
20
|
}
|
|
21
|
+
function isContextualPromptDiffResponse(response) {
|
|
22
|
+
return response.type === "replace" || response.type === "insert";
|
|
23
|
+
}
|
|
24
|
+
function isResolveContextualPromptResponse(response) {
|
|
25
|
+
return typeof response === "object" && response !== null && typeof response.text === "string" && typeof response.type === "string" && ["insert", "replace", "other"].includes(response.type);
|
|
26
|
+
}
|
|
27
|
+
function getRevertTransaction(tr, editor, storage, doc) {
|
|
28
|
+
if (storage.snapshot) {
|
|
29
|
+
const binding = getYjsBinding(editor);
|
|
30
|
+
if (binding) {
|
|
31
|
+
binding.mapping.clear();
|
|
32
|
+
const docFromSnapshot = createDocFromSnapshot(
|
|
33
|
+
binding.doc,
|
|
34
|
+
storage.snapshot
|
|
35
|
+
);
|
|
36
|
+
const type = docFromSnapshot.getXmlFragment("default");
|
|
37
|
+
const fragmentContent = yXmlFragmentToProseMirrorFragment(
|
|
38
|
+
type,
|
|
39
|
+
editor.state.schema
|
|
40
|
+
);
|
|
41
|
+
tr.setMeta("addToHistory", false);
|
|
42
|
+
tr.replace(
|
|
43
|
+
0,
|
|
44
|
+
editor.state.doc.content.size,
|
|
45
|
+
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
46
|
+
);
|
|
47
|
+
tr.setMeta(ySyncPluginKey, {
|
|
48
|
+
snapshot: null,
|
|
49
|
+
prevSnapshot: null
|
|
50
|
+
});
|
|
51
|
+
if (doc) {
|
|
52
|
+
doc.gc = true;
|
|
53
|
+
}
|
|
54
|
+
storage.snapshot = void 0;
|
|
55
|
+
return tr;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
20
60
|
const AiExtension = Extension.create({
|
|
21
61
|
name: "liveblocksAi",
|
|
22
62
|
addOptions() {
|
|
23
63
|
return {
|
|
24
64
|
doc: void 0,
|
|
25
65
|
pud: void 0,
|
|
26
|
-
|
|
66
|
+
resolveContextualPrompt: () => Promise.reject(),
|
|
27
67
|
name: DEFAULT_AI_NAME
|
|
28
68
|
};
|
|
29
69
|
},
|
|
@@ -43,73 +83,74 @@ const AiExtension = Extension.create({
|
|
|
43
83
|
}
|
|
44
84
|
return true;
|
|
45
85
|
},
|
|
46
|
-
$
|
|
86
|
+
$acceptAiToolbarResponse: () => ({ tr, view }) => {
|
|
47
87
|
const currentState = this.storage.state;
|
|
48
88
|
if (currentState.phase !== "reviewing") {
|
|
49
89
|
return false;
|
|
50
90
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
if (isContextualPromptDiffResponse(currentState.response)) {
|
|
92
|
+
const binding = getYjsBinding(this.editor);
|
|
93
|
+
if (!binding) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const fragmentContent = yXmlFragmentToProseMirrorFragment(
|
|
97
|
+
binding.type,
|
|
98
|
+
this.editor.state.schema
|
|
99
|
+
);
|
|
100
|
+
tr.setMeta("addToHistory", false);
|
|
101
|
+
tr.replace(
|
|
102
|
+
0,
|
|
103
|
+
this.editor.state.doc.content.size,
|
|
104
|
+
new Slice(Fragment.from(fragmentContent), 0, 0)
|
|
105
|
+
);
|
|
106
|
+
tr.setMeta(ySyncPluginKey, {
|
|
107
|
+
snapshot: null,
|
|
108
|
+
prevSnapshot: null
|
|
109
|
+
});
|
|
110
|
+
this.storage.snapshot = void 0;
|
|
111
|
+
} else {
|
|
112
|
+
const block = this.editor.schema.nodes.paragraph || Object.values(this.editor.schema.nodes).find(
|
|
113
|
+
(node) => node.isBlock
|
|
114
|
+
);
|
|
115
|
+
if (!block) {
|
|
116
|
+
throw new Error("Could not insert a new paragraph.");
|
|
117
|
+
}
|
|
118
|
+
const paragraph = block.create(
|
|
119
|
+
{},
|
|
120
|
+
this.editor.schema.text(currentState.response.text)
|
|
121
|
+
);
|
|
122
|
+
tr.insert(this.editor.state.selection.$to.end() + 1, paragraph);
|
|
123
|
+
view.dispatch(tr);
|
|
124
|
+
tr.setMeta("preventDispatch", true);
|
|
54
125
|
}
|
|
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
126
|
getLiveblocksYjsProvider(this.editor)?.unpause();
|
|
70
127
|
this.editor.setEditable(true);
|
|
71
|
-
this.storage.snapshot = void 0;
|
|
72
128
|
this.storage.state = { phase: "closed" };
|
|
73
129
|
return true;
|
|
74
130
|
},
|
|
75
|
-
$closeAiToolbar: () => ({ tr }) => {
|
|
131
|
+
$closeAiToolbar: () => ({ tr, view }) => {
|
|
76
132
|
const currentState = this.storage.state;
|
|
133
|
+
if (currentState.phase === "closed") {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
77
136
|
if (currentState.phase === "thinking") {
|
|
78
137
|
currentState.abortController.abort();
|
|
79
138
|
}
|
|
80
139
|
if (currentState.phase === "thinking" || currentState.phase === "reviewing") {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
}
|
|
140
|
+
const revertTr = getRevertTransaction(
|
|
141
|
+
tr,
|
|
142
|
+
this.editor,
|
|
143
|
+
this.storage,
|
|
144
|
+
this.options.doc
|
|
145
|
+
);
|
|
146
|
+
if (revertTr) {
|
|
147
|
+
view.dispatch(revertTr);
|
|
148
|
+
tr.setMeta("preventDispatch", true);
|
|
110
149
|
}
|
|
111
|
-
this.editor.setEditable(true);
|
|
112
150
|
}
|
|
151
|
+
this.editor.commands.setTextSelection(currentState.initialSelection);
|
|
152
|
+
getLiveblocksYjsProvider(this.editor)?.unpause();
|
|
153
|
+
this.editor.setEditable(true);
|
|
113
154
|
this.storage.state = { phase: "closed" };
|
|
114
155
|
return true;
|
|
115
156
|
},
|
|
@@ -123,56 +164,84 @@ const AiExtension = Extension.create({
|
|
|
123
164
|
}
|
|
124
165
|
this.storage.state = {
|
|
125
166
|
phase: "asking",
|
|
167
|
+
initialSelection: {
|
|
168
|
+
from: this.editor.state.selection.from,
|
|
169
|
+
to: this.editor.state.selection.to
|
|
170
|
+
},
|
|
126
171
|
customPrompt: ""
|
|
127
172
|
};
|
|
128
173
|
return true;
|
|
129
174
|
},
|
|
130
|
-
$startAiToolbarThinking: (prompt) => () => {
|
|
175
|
+
$startAiToolbarThinking: (prompt, withPreviousResponse) => ({ tr, view }) => {
|
|
131
176
|
const currentState = this.storage.state;
|
|
132
177
|
if (currentState.phase === "thinking") {
|
|
133
178
|
return false;
|
|
134
179
|
}
|
|
135
|
-
if (currentState.phase === "reviewing") {
|
|
136
|
-
return this.editor.commands.$closeAiToolbar();
|
|
137
|
-
}
|
|
138
180
|
if (this.editor.isFocused) {
|
|
139
181
|
this.editor.commands.blur();
|
|
140
182
|
}
|
|
141
183
|
const abortController = new AbortController();
|
|
142
184
|
const provider = getLiveblocksYjsProvider(this.editor);
|
|
185
|
+
if (currentState.phase === "reviewing") {
|
|
186
|
+
const revertTr = getRevertTransaction(
|
|
187
|
+
tr,
|
|
188
|
+
this.editor,
|
|
189
|
+
this.storage,
|
|
190
|
+
this.options.doc
|
|
191
|
+
);
|
|
192
|
+
if (revertTr) {
|
|
193
|
+
view.dispatch(revertTr);
|
|
194
|
+
tr.setMeta("preventDispatch", true);
|
|
195
|
+
}
|
|
196
|
+
this.editor.commands.setTextSelection(
|
|
197
|
+
currentState.initialSelection
|
|
198
|
+
);
|
|
199
|
+
}
|
|
143
200
|
this.storage.state = {
|
|
144
201
|
phase: "thinking",
|
|
202
|
+
initialSelection: currentState.initialSelection ?? {
|
|
203
|
+
from: this.editor.state.selection.from,
|
|
204
|
+
to: this.editor.state.selection.to
|
|
205
|
+
},
|
|
145
206
|
customPrompt: currentState.customPrompt ?? "",
|
|
146
207
|
prompt,
|
|
147
|
-
abortController
|
|
208
|
+
abortController,
|
|
209
|
+
previousResponse: currentState.response
|
|
148
210
|
};
|
|
149
211
|
this.editor.setEditable(false);
|
|
150
212
|
autoRetry(
|
|
151
213
|
async () => {
|
|
152
214
|
await provider?.pause();
|
|
153
|
-
const
|
|
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({
|
|
215
|
+
const response = await this.options.resolveContextualPrompt({
|
|
158
216
|
prompt,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
217
|
+
context: getContextualPromptContext(this.editor, 3e3),
|
|
218
|
+
signal: abortController.signal,
|
|
219
|
+
previous: withPreviousResponse && currentState.phase === "reviewing" ? {
|
|
220
|
+
prompt: currentState.prompt,
|
|
221
|
+
response: {
|
|
222
|
+
type: currentState.response?.type,
|
|
223
|
+
text: currentState.response?.text
|
|
224
|
+
}
|
|
225
|
+
} : void 0
|
|
162
226
|
});
|
|
227
|
+
if (isResolveContextualPromptResponse(response)) {
|
|
228
|
+
return response;
|
|
229
|
+
} else {
|
|
230
|
+
throw new Error("Failed to resolve AI prompt.");
|
|
231
|
+
}
|
|
163
232
|
},
|
|
164
233
|
RESOLVE_AI_PROMPT_RETRY_ATTEMPTS,
|
|
165
234
|
RESOLVE_AI_PROMPT_RETRY_DELAYS,
|
|
166
235
|
(error) => {
|
|
167
236
|
return abortController.signal.aborted || error instanceof HttpError && error.status >= 400 && error.status < 500;
|
|
168
237
|
}
|
|
169
|
-
).then((
|
|
238
|
+
).then((response) => {
|
|
170
239
|
if (abortController.signal.aborted) {
|
|
171
240
|
return;
|
|
172
241
|
}
|
|
173
242
|
this.editor.commands._handleAiToolbarThinkingSuccess({
|
|
174
|
-
type:
|
|
175
|
-
text:
|
|
243
|
+
type: response.type,
|
|
244
|
+
text: response.text
|
|
176
245
|
});
|
|
177
246
|
}).catch((error) => {
|
|
178
247
|
if (abortController.signal.aborted) {
|
|
@@ -191,43 +260,64 @@ const AiExtension = Extension.create({
|
|
|
191
260
|
this.editor.setEditable(true);
|
|
192
261
|
this.storage.state = {
|
|
193
262
|
phase: "asking",
|
|
263
|
+
initialSelection: currentState.initialSelection,
|
|
194
264
|
customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : ""
|
|
195
265
|
};
|
|
196
266
|
return true;
|
|
197
267
|
},
|
|
198
|
-
|
|
268
|
+
_showAiToolbarReviewingDiff: () => () => {
|
|
269
|
+
if (this.storage.state.phase !== "reviewing") {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (!this.options.doc || !this.storage.snapshot) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const previousSnapshot = this.storage.snapshot ?? emptySnapshot;
|
|
276
|
+
const currentSnapshot = snapshot(this.options.doc);
|
|
277
|
+
if (equalSnapshots(previousSnapshot, currentSnapshot)) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
getYjsBinding(this.editor)?.renderSnapshot(
|
|
281
|
+
currentSnapshot,
|
|
282
|
+
previousSnapshot
|
|
283
|
+
);
|
|
284
|
+
return true;
|
|
285
|
+
},
|
|
286
|
+
_handleAiToolbarThinkingSuccess: (response) => ({ view, tr }) => {
|
|
199
287
|
const currentState = this.storage.state;
|
|
200
|
-
if (currentState.phase !== "thinking"
|
|
288
|
+
if (currentState.phase !== "thinking") {
|
|
201
289
|
return false;
|
|
202
290
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
291
|
+
if (!isContextualPromptDiffResponse(response)) {
|
|
292
|
+
this.storage.state = {
|
|
293
|
+
phase: "reviewing",
|
|
294
|
+
initialSelection: currentState.initialSelection,
|
|
295
|
+
customPrompt: "",
|
|
296
|
+
prompt: currentState.prompt,
|
|
297
|
+
response
|
|
222
298
|
};
|
|
299
|
+
return true;
|
|
223
300
|
}
|
|
301
|
+
if (!this.options.doc) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
this.options.doc.gc = false;
|
|
305
|
+
this.storage.snapshot = snapshot(this.options.doc);
|
|
224
306
|
this.storage.state = {
|
|
225
307
|
phase: "reviewing",
|
|
308
|
+
initialSelection: currentState.initialSelection,
|
|
226
309
|
customPrompt: "",
|
|
227
310
|
prompt: currentState.prompt,
|
|
228
|
-
|
|
229
|
-
selection
|
|
311
|
+
response
|
|
230
312
|
};
|
|
313
|
+
tr.insertText(
|
|
314
|
+
response.text,
|
|
315
|
+
response.type === "insert" ? this.editor.state.selection.to : this.editor.state.selection.from,
|
|
316
|
+
this.editor.state.selection.to
|
|
317
|
+
);
|
|
318
|
+
view.dispatch(tr);
|
|
319
|
+
tr.setMeta("preventDispatch", true);
|
|
320
|
+
this.editor.commands._showAiToolbarReviewingDiff();
|
|
231
321
|
return true;
|
|
232
322
|
},
|
|
233
323
|
_handleAiToolbarThinkingError: (error) => () => {
|
|
@@ -236,10 +326,12 @@ const AiExtension = Extension.create({
|
|
|
236
326
|
return false;
|
|
237
327
|
}
|
|
238
328
|
this.editor.setEditable(true);
|
|
329
|
+
console.error(error);
|
|
239
330
|
this.storage.state = {
|
|
240
331
|
phase: "asking",
|
|
332
|
+
initialSelection: currentState.initialSelection,
|
|
241
333
|
customPrompt: currentState.prompt === currentState.customPrompt ? currentState.customPrompt : "",
|
|
242
|
-
error
|
|
334
|
+
error: error instanceof Error ? error : new Error(String(error), { cause: error })
|
|
243
335
|
};
|
|
244
336
|
return true;
|
|
245
337
|
},
|
|
@@ -250,22 +342,6 @@ const AiExtension = Extension.create({
|
|
|
250
342
|
}
|
|
251
343
|
this.storage.state.customPrompt = typeof customPrompt === "function" ? customPrompt(currentState.customPrompt) : customPrompt;
|
|
252
344
|
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
345
|
}
|
|
270
346
|
};
|
|
271
347
|
},
|
|
@@ -275,10 +351,10 @@ const AiExtension = Extension.create({
|
|
|
275
351
|
key: AI_TOOLBAR_SELECTION_PLUGIN,
|
|
276
352
|
props: {
|
|
277
353
|
decorations: ({ doc, selection }) => {
|
|
278
|
-
if (this.storage.state.phase === "closed" || this.storage.state.phase === "reviewing" && this.storage.state.
|
|
354
|
+
if (this.storage.state.phase === "closed" || this.storage.state.phase === "reviewing" && isContextualPromptDiffResponse(this.storage.state.response)) {
|
|
279
355
|
return DecorationSet.create(doc, []);
|
|
280
356
|
}
|
|
281
|
-
const { from, to } =
|
|
357
|
+
const { from, to } = selection;
|
|
282
358
|
const decorations = [
|
|
283
359
|
Decoration.inline(from, to, {
|
|
284
360
|
class: "lb-root lb-selection lb-tiptap-active-selection"
|
|
@@ -292,5 +368,5 @@ const AiExtension = Extension.create({
|
|
|
292
368
|
}
|
|
293
369
|
});
|
|
294
370
|
|
|
295
|
-
export { AiExtension, DEFAULT_STATE };
|
|
371
|
+
export { AiExtension, DEFAULT_STATE, isContextualPromptDiffResponse };
|
|
296
372
|
//# sourceMappingURL=AiExtension.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiExtension.mjs","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":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiExtension.mjs","sources":["../../src/ai/AiExtension.ts"],"sourcesContent":["import {\n autoRetry,\n type ContextualPromptResponse,\n HttpError,\n} 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 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 { Doc, Snapshot } from \"yjs\";\nimport {\n createDocFromSnapshot,\n emptySnapshot,\n equalSnapshots,\n snapshot as takeSnapshot,\n} from \"yjs\";\n\nimport {\n AI_TOOLBAR_SELECTION_PLUGIN,\n type AiCommands,\n type AiExtensionOptions,\n type AiExtensionStorage,\n type AiToolbarState,\n type LiveblocksExtensionStorage,\n type ResolveContextualPromptResponse,\n type YSyncPluginState,\n} from \"../types\";\nimport { getContextualPromptContext } from \"../utils\";\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 function isContextualPromptDiffResponse(\n response: ContextualPromptResponse\n): response is Extract<\n ContextualPromptResponse,\n { type: \"replace\" | \"insert\" }\n> {\n return response.type === \"replace\" || response.type === \"insert\";\n}\n\nfunction isResolveContextualPromptResponse(\n response: unknown\n): response is ResolveContextualPromptResponse {\n return (\n typeof response === \"object\" &&\n response !== null &&\n typeof (response as { text: unknown }).text === \"string\" &&\n typeof (response as { type: unknown }).type === \"string\" &&\n [\"insert\", \"replace\", \"other\"].includes((response as { type: string }).type)\n );\n}\n\nfunction getRevertTransaction(\n tr: Transaction,\n editor: Editor,\n storage: AiExtensionStorage,\n doc?: Doc\n): Transaction | null {\n if (storage.snapshot) {\n const binding = getYjsBinding(editor);\n\n if (binding) {\n binding.mapping.clear();\n\n const docFromSnapshot = createDocFromSnapshot(\n binding.doc,\n storage.snapshot\n );\n const type = docFromSnapshot.getXmlFragment(\"default\"); // TODO: field\n const fragmentContent = yXmlFragmentToProseMirrorFragment(\n type,\n editor.state.schema\n );\n\n tr.setMeta(\"addToHistory\", false);\n tr.replace(\n 0,\n 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 if (doc) {\n doc.gc = true;\n }\n\n storage.snapshot = undefined;\n\n return tr;\n }\n }\n return null;\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 resolveContextualPrompt: () => 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 $acceptAiToolbarResponse:\n () =>\n ({ tr, view }: CommandProps) => {\n const currentState = this.storage.state;\n\n // 1. If NOT in \"reviewing\" phase, do nothing\n if (currentState.phase !== \"reviewing\") {\n return false;\n }\n\n // 2. Accept the response\n if (isContextualPromptDiffResponse(currentState.response)) {\n // 2.a. If the response is a diff, apply it definitely\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 this.storage.snapshot = undefined;\n } else {\n // 2.b. If the response is not a diff, insert it below the selection\n const block =\n this.editor.schema.nodes.paragraph ||\n Object.values(this.editor.schema.nodes).find(\n (node) => node.isBlock\n );\n\n if (!block) {\n throw new Error(\"Could not insert a new paragraph.\");\n }\n\n const paragraph = block.create(\n {},\n this.editor.schema.text(currentState.response.text)\n );\n\n tr.insert(this.editor.state.selection.$to.end() + 1, paragraph);\n view.dispatch(tr);\n // Prevent TipTap from dispatching this transaction, because we already did (this is a hack)\n tr.setMeta(\"preventDispatch\", true);\n }\n\n // 3. Unblock the editor\n getLiveblocksYjsProvider(this.editor)?.unpause();\n this.editor.setEditable(true);\n\n // 4. Set to \"closed\" phase\n this.storage.state = { phase: \"closed\" };\n\n return true;\n },\n\n $closeAiToolbar:\n () =>\n ({ tr, view }: CommandProps) => {\n const currentState = this.storage.state;\n\n // 1. If already in \"closed\" phase, do nothing\n if (currentState.phase === \"closed\") {\n return false;\n }\n\n // 2. If in \"thinking\" phase, cancel the current AI request\n if (currentState.phase === \"thinking\") {\n currentState.abortController.abort();\n }\n\n // 3. 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 // Get the revert transaction and dispatch it\n const revertTr = getRevertTransaction(\n tr,\n this.editor,\n this.storage,\n this.options.doc\n );\n if (revertTr) {\n view.dispatch(revertTr);\n // Prevent TipTap from dispatching this transaction, because we already did (this is a hack)\n tr.setMeta(\"preventDispatch\", true);\n }\n }\n\n // 4. Restore the initial selection\n this.editor.commands.setTextSelection(currentState.initialSelection);\n\n // 5. Unblock the editor\n getLiveblocksYjsProvider(this.editor)?.unpause();\n this.editor.setEditable(true);\n\n // 6. 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 // Store the initial selection\n initialSelection: {\n from: this.editor.state.selection.from,\n to: this.editor.state.selection.to,\n },\n // Initialize the custom prompt as empty\n customPrompt: \"\",\n };\n\n return true;\n },\n\n $startAiToolbarThinking:\n (prompt: string, withPreviousResponse?: boolean) =>\n ({ tr, view }: CommandProps) => {\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 // 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. If this is a retry or a refinement, revert the editor and restore the initial selection\n if (currentState.phase === \"reviewing\") {\n // 3.a. Revert the editor\n const revertTr = getRevertTransaction(\n tr,\n this.editor,\n this.storage,\n this.options.doc\n );\n if (revertTr) {\n // Important: in this scenario we do not unpause the provider\n view.dispatch(revertTr);\n // Prevent Tiptap from dispatching this transaction, because we already did (this is a hack)\n tr.setMeta(\"preventDispatch\", true);\n }\n\n // 3.b. Restore the initial selection\n this.editor.commands.setTextSelection(\n currentState.initialSelection\n );\n }\n\n // 4. Set to \"thinking\" phase\n this.storage.state = {\n phase: \"thinking\",\n // Store the initial selection if the toolbar is opened directly in the \"thinking\" phase\n initialSelection: currentState.initialSelection ?? {\n from: this.editor.state.selection.from,\n to: this.editor.state.selection.to,\n },\n // Initialize the custom prompt as empty if the toolbar is opened directly in the \"thinking\" phase\n customPrompt: currentState.customPrompt ?? \"\",\n prompt,\n abortController,\n previousResponse: currentState.response,\n };\n\n // 5. Block the editor\n this.editor.setEditable(false);\n\n // 6. Start the AI request\n autoRetry(\n async () => {\n await provider?.pause();\n\n // 6.a. Resolve the AI prompt\n const response = (await this.options.resolveContextualPrompt({\n prompt,\n context: getContextualPromptContext(this.editor, 3_000),\n signal: abortController.signal,\n previous:\n withPreviousResponse && currentState.phase === \"reviewing\"\n ? {\n prompt: currentState.prompt,\n response: {\n type: currentState.response?.type,\n text: currentState.response?.text,\n },\n }\n : undefined,\n })) as unknown;\n\n // 6.b. Validate the response\n if (isResolveContextualPromptResponse(response)) {\n return response;\n } else {\n throw new Error(\"Failed to resolve AI prompt.\");\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((response) => {\n if (abortController.signal.aborted) {\n return;\n }\n\n // 6.a. If the AI request succeeds, set to \"reviewing\" phase with the response\n (\n this.editor.commands as unknown as AiCommands\n )._handleAiToolbarThinkingSuccess({\n type: response.type,\n text: response.text,\n });\n })\n .catch((error) => {\n if (abortController.signal.aborted) {\n return;\n }\n\n // 6.b. If the AI request fails, set to \"asking\" phase with error\n (\n this.editor.commands as unknown as AiCommands\n )._handleAiToolbarThinkingError(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 initialSelection: currentState.initialSelection,\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 _showAiToolbarReviewingDiff: () => () => {\n // The diff is applied right before moving to the \"reviewing\" phase\n if (this.storage.state.phase !== \"reviewing\") {\n return false;\n }\n\n if (!this.options.doc || !this.storage.snapshot) {\n return false;\n }\n\n const previousSnapshot: Snapshot =\n this.storage.snapshot ?? emptySnapshot;\n const currentSnapshot = takeSnapshot(this.options.doc);\n\n if (equalSnapshots(previousSnapshot, currentSnapshot)) {\n return true;\n }\n\n getYjsBinding(this.editor)?.renderSnapshot(\n currentSnapshot,\n previousSnapshot\n );\n\n return true;\n },\n\n _handleAiToolbarThinkingSuccess:\n (response: ContextualPromptResponse) =>\n ({ view, tr }: CommandProps) => {\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. If this is not a diff, the response will just be in the toolbar, set to \"reviewing\" phase directly with the response\n if (!isContextualPromptDiffResponse(response)) {\n this.storage.state = {\n phase: \"reviewing\",\n initialSelection: currentState.initialSelection,\n customPrompt: \"\",\n prompt: currentState.prompt,\n response,\n };\n\n return true;\n }\n\n if (!this.options.doc) {\n return false;\n }\n\n // 3. Otherwise, the response is a diff, apply it to the editor\n this.options.doc.gc = false;\n this.storage.snapshot = takeSnapshot(this.options.doc);\n\n // 4. Set to \"reviewing\" phase with the new range\n this.storage.state = {\n phase: \"reviewing\",\n initialSelection: currentState.initialSelection,\n customPrompt: \"\",\n prompt: currentState.prompt,\n response,\n };\n\n // 5. Insert the response's text\n tr.insertText(\n response.text,\n response.type === \"insert\"\n ? this.editor.state.selection.to\n : this.editor.state.selection.from,\n this.editor.state.selection.to\n );\n view.dispatch(tr);\n // Prevent Tiptap from dispatching this transaction, because we already did (this is a hack)\n tr.setMeta(\"preventDispatch\", true);\n\n // 6. Show the diff\n (\n this.editor.commands as unknown as AiCommands\n )._showAiToolbarReviewingDiff();\n\n // We moved to the \"reviewing\" phase, so even if `_showAiToolbarReviewingDiff`\n // returns `false` somehow, we still want to return `true`\n return true;\n },\n\n _handleAiToolbarThinkingError: (error: unknown) => () => {\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. Log the error\n console.error(error);\n\n // 4. Set to \"asking\" phase with error\n this.storage.state = {\n phase: \"asking\",\n initialSelection: currentState.initialSelection,\n // If the custom prompt is different than the prompt, reset it\n customPrompt:\n currentState.prompt === currentState.customPrompt\n ? currentState.customPrompt\n : \"\",\n // Pass the error so it can be displayed\n error:\n error instanceof Error\n ? error\n : new Error(String(error), { cause: 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 },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: AI_TOOLBAR_SELECTION_PLUGIN,\n props: {\n decorations: ({ doc, selection }) => {\n // Don't show the AI toolbar selection if the toolbar is closed or when reviewing diff responses\n if (\n this.storage.state.phase === \"closed\" ||\n (this.storage.state.phase === \"reviewing\" &&\n isContextualPromptDiffResponse(this.storage.state.response))\n ) {\n return DecorationSet.create(doc, []);\n }\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\n return DecorationSet.create(doc, decorations);\n },\n },\n }),\n ];\n },\n});\n"],"names":["takeSnapshot"],"mappings":"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"}
|