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