@platejs/ai 51.1.1 → 52.0.0
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/getEditorPrompt-BnVrgUl3.js +102 -0
- package/dist/index-CyVv31EV.d.ts +96 -0
- package/dist/index.d.ts +2 -22
- package/dist/index.js +47 -224
- package/dist/react/index.d.ts +469 -377
- package/dist/react/index.js +1252 -1927
- package/package.json +14 -22
- package/dist/getEditorPrompt-C7EEuMw8.d.mts +0 -54
- package/dist/getEditorPrompt-C7EEuMw8.d.ts +0 -54
- package/dist/index.d.mts +0 -22
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -193
- package/dist/index.mjs.map +0 -1
- package/dist/react/index.d.mts +0 -471
- package/dist/react/index.js.map +0 -1
- package/dist/react/index.mjs +0 -1984
- package/dist/react/index.mjs.map +0 -1
package/dist/react/index.mjs
DELETED
|
@@ -1,1984 +0,0 @@
|
|
|
1
|
-
// src/react/ai/AIPlugin.ts
|
|
2
|
-
import { toPlatePlugin } from "platejs/react";
|
|
3
|
-
|
|
4
|
-
// src/lib/BaseAIPlugin.ts
|
|
5
|
-
import {
|
|
6
|
-
bindFirst,
|
|
7
|
-
createTSlatePlugin,
|
|
8
|
-
KEYS as KEYS2
|
|
9
|
-
} from "platejs";
|
|
10
|
-
|
|
11
|
-
// src/lib/transforms/insertAINodes.ts
|
|
12
|
-
var insertAINodes = (editor, nodes, {
|
|
13
|
-
target
|
|
14
|
-
} = {}) => {
|
|
15
|
-
if (!target && !editor.selection?.focus.path) return;
|
|
16
|
-
const aiNodes = nodes.map((node) => ({
|
|
17
|
-
...node,
|
|
18
|
-
ai: true
|
|
19
|
-
}));
|
|
20
|
-
editor.tf.withoutNormalizing(() => {
|
|
21
|
-
editor.tf.insertNodes(aiNodes, {
|
|
22
|
-
at: editor.api.end(target || editor.selection.focus.path),
|
|
23
|
-
select: true
|
|
24
|
-
});
|
|
25
|
-
editor.tf.collapse({ edge: "end" });
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// src/lib/transforms/removeAIMarks.ts
|
|
30
|
-
import { getPluginType, KEYS } from "platejs";
|
|
31
|
-
var removeAIMarks = (editor, { at = [] } = {}) => {
|
|
32
|
-
const nodeType = getPluginType(editor, KEYS.ai);
|
|
33
|
-
editor.tf.unsetNodes(nodeType, {
|
|
34
|
-
at,
|
|
35
|
-
match: (n) => n[nodeType]
|
|
36
|
-
});
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// src/lib/transforms/removeAINodes.ts
|
|
40
|
-
import { TextApi } from "platejs";
|
|
41
|
-
var removeAINodes = (editor, { at = [] } = {}) => {
|
|
42
|
-
editor.tf.removeNodes({
|
|
43
|
-
at,
|
|
44
|
-
match: (n) => TextApi.isText(n) && !!n.ai
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// src/lib/transforms/undoAI.ts
|
|
49
|
-
import { getTransientSuggestionKey } from "@platejs/suggestion";
|
|
50
|
-
var undoAI = (editor) => {
|
|
51
|
-
const hasAINodeOrAISuggestion = editor.api.some({
|
|
52
|
-
at: [],
|
|
53
|
-
match: (n) => !!n.ai
|
|
54
|
-
}) || editor.api.some({
|
|
55
|
-
at: [],
|
|
56
|
-
match: (n) => !!n[getTransientSuggestionKey()]
|
|
57
|
-
});
|
|
58
|
-
if (editor.history.undos.at(-1)?.ai && hasAINodeOrAISuggestion) {
|
|
59
|
-
editor.undo();
|
|
60
|
-
editor.history.redos.pop();
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// src/lib/transforms/withAIBatch.ts
|
|
65
|
-
var withAIBatch = (editor, fn, {
|
|
66
|
-
split
|
|
67
|
-
} = {}) => {
|
|
68
|
-
if (split) {
|
|
69
|
-
editor.tf.withNewBatch(fn);
|
|
70
|
-
} else {
|
|
71
|
-
editor.tf.withMerging(fn);
|
|
72
|
-
}
|
|
73
|
-
const lastBatch = editor.history.undos?.at(-1);
|
|
74
|
-
if (lastBatch) {
|
|
75
|
-
lastBatch.ai = true;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// src/lib/BaseAIPlugin.ts
|
|
80
|
-
var BaseAIPlugin = createTSlatePlugin({
|
|
81
|
-
key: KEYS2.ai,
|
|
82
|
-
node: { isDecoration: false, isLeaf: true }
|
|
83
|
-
}).extendTransforms(({ editor }) => ({
|
|
84
|
-
insertNodes: bindFirst(insertAINodes, editor),
|
|
85
|
-
removeMarks: bindFirst(removeAIMarks, editor),
|
|
86
|
-
removeNodes: bindFirst(removeAINodes, editor),
|
|
87
|
-
undo: bindFirst(undoAI, editor)
|
|
88
|
-
}));
|
|
89
|
-
|
|
90
|
-
// src/lib/utils/getEditorPrompt.ts
|
|
91
|
-
import { isSelecting } from "@platejs/selection";
|
|
92
|
-
import { KEYS as KEYS3 } from "platejs";
|
|
93
|
-
var createPromptFromConfig = (config, params) => {
|
|
94
|
-
const { isBlockSelecting, isSelecting: isSelecting3 } = params;
|
|
95
|
-
if (isBlockSelecting && config.blockSelecting) {
|
|
96
|
-
return config.blockSelecting ?? config.default;
|
|
97
|
-
} else if (isSelecting3 && config.selecting) {
|
|
98
|
-
return config.selecting ?? config.default;
|
|
99
|
-
} else {
|
|
100
|
-
return config.default;
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
var getEditorPrompt = (editor, {
|
|
104
|
-
prompt = ""
|
|
105
|
-
}) => {
|
|
106
|
-
const params = {
|
|
107
|
-
editor,
|
|
108
|
-
isBlockSelecting: editor.getOption(
|
|
109
|
-
{ key: KEYS3.blockSelection },
|
|
110
|
-
"isSelectingSome"
|
|
111
|
-
),
|
|
112
|
-
isSelecting: isSelecting(editor)
|
|
113
|
-
};
|
|
114
|
-
let promptText = "";
|
|
115
|
-
if (typeof prompt === "function") {
|
|
116
|
-
promptText = prompt(params);
|
|
117
|
-
} else if (typeof prompt === "object") {
|
|
118
|
-
promptText = createPromptFromConfig(prompt, params);
|
|
119
|
-
} else {
|
|
120
|
-
promptText = prompt;
|
|
121
|
-
}
|
|
122
|
-
return promptText;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// src/react/ai/AIPlugin.ts
|
|
126
|
-
var AIPlugin = toPlatePlugin(BaseAIPlugin);
|
|
127
|
-
|
|
128
|
-
// src/react/ai/utils/aiCommentToRange.ts
|
|
129
|
-
import { deserializeMd } from "@platejs/markdown";
|
|
130
|
-
import {
|
|
131
|
-
NodeApi as NodeApi2
|
|
132
|
-
} from "platejs";
|
|
133
|
-
|
|
134
|
-
// src/react/ai/utils/findTextRangeInBlock.tsx
|
|
135
|
-
import { distance } from "fastest-levenshtein";
|
|
136
|
-
import { NodeApi } from "platejs";
|
|
137
|
-
function maxAllowedDistance(len) {
|
|
138
|
-
if (len <= 2) return 0;
|
|
139
|
-
if (len <= 5) return 1;
|
|
140
|
-
if (len <= 10) return 2;
|
|
141
|
-
if (len <= 20) return 3;
|
|
142
|
-
return 5;
|
|
143
|
-
}
|
|
144
|
-
function findTextRangeInBlock({
|
|
145
|
-
block,
|
|
146
|
-
findText
|
|
147
|
-
}) {
|
|
148
|
-
const [blockNode, blockPath] = block;
|
|
149
|
-
const textSegments = [];
|
|
150
|
-
let fullText = "";
|
|
151
|
-
for (const [textNode, textPath] of NodeApi.texts(blockNode)) {
|
|
152
|
-
const startOffset = fullText.length;
|
|
153
|
-
const absolutePath = [...blockPath, ...textPath];
|
|
154
|
-
textSegments.push({
|
|
155
|
-
offset: startOffset,
|
|
156
|
-
path: absolutePath,
|
|
157
|
-
text: textNode.text
|
|
158
|
-
});
|
|
159
|
-
fullText += textNode.text;
|
|
160
|
-
}
|
|
161
|
-
if (!fullText) return null;
|
|
162
|
-
let matchStart = fullText.indexOf(findText);
|
|
163
|
-
let matchEnd = matchStart >= 0 ? matchStart + findText.length : -1;
|
|
164
|
-
if (matchStart === -1) {
|
|
165
|
-
const maxDist = maxAllowedDistance(findText.length);
|
|
166
|
-
let bestMatch = { distance: Infinity, end: -1, start: -1 };
|
|
167
|
-
for (let i = 0; i <= fullText.length - findText.length; i++) {
|
|
168
|
-
for (let lenOffset = -maxDist; lenOffset <= maxDist; lenOffset++) {
|
|
169
|
-
const len = findText.length + lenOffset;
|
|
170
|
-
if (len <= 0 || i + len > fullText.length) continue;
|
|
171
|
-
const candidate = fullText.slice(i, i + len);
|
|
172
|
-
const dist = distance(candidate, findText);
|
|
173
|
-
if (dist <= maxDist && dist < bestMatch.distance) {
|
|
174
|
-
bestMatch = { distance: dist, end: i + len, start: i };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (bestMatch.start !== -1) {
|
|
179
|
-
matchStart = bestMatch.start;
|
|
180
|
-
matchEnd = bestMatch.end;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (matchStart === -1) {
|
|
184
|
-
for (let prefixLen = findText.length - 1; prefixLen > 0; prefixLen--) {
|
|
185
|
-
const prefix = findText.slice(0, Math.max(0, prefixLen));
|
|
186
|
-
const idx = fullText.indexOf(prefix);
|
|
187
|
-
if (idx !== -1) {
|
|
188
|
-
matchStart = idx;
|
|
189
|
-
matchEnd = idx + prefixLen;
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (matchStart === -1) return null;
|
|
195
|
-
const findPoint = (charOffset, isEnd = false) => {
|
|
196
|
-
const accumulated = 0;
|
|
197
|
-
if (!isEnd) {
|
|
198
|
-
for (const segment of textSegments) {
|
|
199
|
-
if (charOffset === segment.offset) {
|
|
200
|
-
return {
|
|
201
|
-
offset: 0,
|
|
202
|
-
path: segment.path
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
for (const segment of textSegments) {
|
|
208
|
-
const segmentEnd = segment.offset + segment.text.length;
|
|
209
|
-
if (charOffset >= segment.offset && charOffset <= segmentEnd) {
|
|
210
|
-
const localOffset = charOffset - segment.offset;
|
|
211
|
-
return {
|
|
212
|
-
offset: localOffset,
|
|
213
|
-
path: segment.path
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const lastSegment = textSegments.at(-1);
|
|
218
|
-
if (!lastSegment) {
|
|
219
|
-
return { offset: 0, path: blockPath };
|
|
220
|
-
}
|
|
221
|
-
return {
|
|
222
|
-
offset: lastSegment.text.length,
|
|
223
|
-
path: lastSegment.path
|
|
224
|
-
};
|
|
225
|
-
};
|
|
226
|
-
const anchor = findPoint(matchStart, false);
|
|
227
|
-
const focus = findPoint(matchEnd, true);
|
|
228
|
-
return {
|
|
229
|
-
anchor,
|
|
230
|
-
focus
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// src/react/ai/utils/aiCommentToRange.ts
|
|
235
|
-
var aiCommentToRange = (editor, aiComment) => {
|
|
236
|
-
const { blockId, content } = aiComment;
|
|
237
|
-
const contentNodes = deserializeMd(editor, content);
|
|
238
|
-
let firstBlock;
|
|
239
|
-
const ranges = [];
|
|
240
|
-
contentNodes.forEach((node, index) => {
|
|
241
|
-
let currentBlock;
|
|
242
|
-
if (index === 0) {
|
|
243
|
-
firstBlock = editor.api.node({ id: blockId, at: [] });
|
|
244
|
-
currentBlock = firstBlock;
|
|
245
|
-
} else {
|
|
246
|
-
if (!firstBlock) return;
|
|
247
|
-
const [_, firstBlockPath] = firstBlock;
|
|
248
|
-
const blockPath = [firstBlockPath[0] + index];
|
|
249
|
-
currentBlock = editor.api.node(blockPath);
|
|
250
|
-
}
|
|
251
|
-
if (!currentBlock) return;
|
|
252
|
-
const range = findTextRangeInBlock({
|
|
253
|
-
block: currentBlock,
|
|
254
|
-
findText: NodeApi2.string(node)
|
|
255
|
-
});
|
|
256
|
-
if (!range) return;
|
|
257
|
-
ranges.push(range);
|
|
258
|
-
});
|
|
259
|
-
if (ranges.length === 0) return;
|
|
260
|
-
if (ranges.length > 1) {
|
|
261
|
-
const startRange = ranges[0];
|
|
262
|
-
const endRange = ranges.at(-1);
|
|
263
|
-
return {
|
|
264
|
-
anchor: startRange.anchor,
|
|
265
|
-
focus: endRange.focus
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
if (ranges.length === 1) {
|
|
269
|
-
return ranges[0];
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// src/react/ai-chat/AIChatPlugin.ts
|
|
274
|
-
import { BlockSelectionPlugin as BlockSelectionPlugin5 } from "@platejs/selection/react";
|
|
275
|
-
import {
|
|
276
|
-
bindFirst as bindFirst2,
|
|
277
|
-
ElementApi as ElementApi4,
|
|
278
|
-
getPluginType as getPluginType3,
|
|
279
|
-
KEYS as KEYS12
|
|
280
|
-
} from "platejs";
|
|
281
|
-
import { createTPlatePlugin } from "platejs/react";
|
|
282
|
-
|
|
283
|
-
// src/react/ai-chat/transforms/acceptAIChat.ts
|
|
284
|
-
import { KEYS as KEYS4 } from "platejs";
|
|
285
|
-
import { getEditorPlugin } from "platejs/react";
|
|
286
|
-
|
|
287
|
-
// src/react/ai-chat/utils/acceptAISuggestions.ts
|
|
288
|
-
import { acceptSuggestion, getSuggestionKey, getTransientSuggestionKey as getTransientSuggestionKey2 } from "@platejs/suggestion";
|
|
289
|
-
import { SuggestionPlugin } from "@platejs/suggestion/react";
|
|
290
|
-
var acceptAISuggestions = (editor) => {
|
|
291
|
-
const suggestions = editor.getApi(SuggestionPlugin).suggestion.nodes({
|
|
292
|
-
transient: true
|
|
293
|
-
});
|
|
294
|
-
suggestions.forEach(([suggestionNode]) => {
|
|
295
|
-
const suggestionData = editor.getApi(SuggestionPlugin).suggestion.suggestionData(suggestionNode);
|
|
296
|
-
if (!suggestionData) return;
|
|
297
|
-
const description = {
|
|
298
|
-
createdAt: new Date(suggestionData.createdAt),
|
|
299
|
-
keyId: getSuggestionKey(suggestionData.id),
|
|
300
|
-
suggestionId: suggestionData.id,
|
|
301
|
-
type: suggestionData.type,
|
|
302
|
-
userId: suggestionData.userId
|
|
303
|
-
};
|
|
304
|
-
acceptSuggestion(editor, description);
|
|
305
|
-
});
|
|
306
|
-
editor.tf.unsetNodes([getTransientSuggestionKey2()], {
|
|
307
|
-
at: [],
|
|
308
|
-
mode: "all",
|
|
309
|
-
match: (n) => !!n[getTransientSuggestionKey2()]
|
|
310
|
-
});
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// src/react/ai-chat/transforms/acceptAIChat.ts
|
|
314
|
-
var acceptAIChat = (editor) => {
|
|
315
|
-
const mode = editor.getOption(AIChatPlugin, "mode");
|
|
316
|
-
if (mode === "insert") {
|
|
317
|
-
const { tf } = getEditorPlugin(editor, AIPlugin);
|
|
318
|
-
const api = editor.getApi({ key: KEYS4.ai });
|
|
319
|
-
const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
|
|
320
|
-
withAIBatch(editor, () => {
|
|
321
|
-
tf.ai.removeMarks();
|
|
322
|
-
editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
|
|
323
|
-
});
|
|
324
|
-
api.aiChat.hide();
|
|
325
|
-
editor.tf.focus();
|
|
326
|
-
const focusPoint = editor.api.end(lastAINodePath);
|
|
327
|
-
editor.tf.setSelection({
|
|
328
|
-
anchor: focusPoint,
|
|
329
|
-
focus: focusPoint
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
if (mode === "chat") {
|
|
333
|
-
withAIBatch(editor, () => {
|
|
334
|
-
acceptAISuggestions(editor);
|
|
335
|
-
});
|
|
336
|
-
editor.getApi(AIChatPlugin).aiChat.hide();
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
// src/react/ai-chat/transforms/insertBelowAIChat.ts
|
|
341
|
-
import { BlockSelectionPlugin as BlockSelectionPlugin4 } from "@platejs/selection/react";
|
|
342
|
-
import cloneDeep2 from "lodash/cloneDeep.js";
|
|
343
|
-
import { KEYS as KEYS9, PathApi, RangeApi } from "platejs";
|
|
344
|
-
|
|
345
|
-
// src/react/ai-chat/utils/applyAISuggestions.ts
|
|
346
|
-
import { deserializeMd as deserializeMd2 } from "@platejs/markdown";
|
|
347
|
-
import { BlockSelectionPlugin } from "@platejs/selection/react";
|
|
348
|
-
import {
|
|
349
|
-
diffToSuggestions,
|
|
350
|
-
getTransientSuggestionKey as getTransientSuggestionKey3,
|
|
351
|
-
SkipSuggestionDeletes
|
|
352
|
-
} from "@platejs/suggestion";
|
|
353
|
-
import {
|
|
354
|
-
ElementApi,
|
|
355
|
-
KEYS as KEYS5,
|
|
356
|
-
nanoid,
|
|
357
|
-
TextApi as TextApi2
|
|
358
|
-
} from "platejs";
|
|
359
|
-
var applyAISuggestions = (editor, content) => {
|
|
360
|
-
editor.getApi({ key: KEYS5.cursorOverlay })?.cursorOverlay?.removeCursor("selection");
|
|
361
|
-
const isBlockSelecting = editor.getOption(
|
|
362
|
-
BlockSelectionPlugin,
|
|
363
|
-
"isSelectingSome"
|
|
364
|
-
);
|
|
365
|
-
const { chatNodes } = editor.getOptions(AIChatPlugin);
|
|
366
|
-
if (isBlockSelecting || editor.api.blocks({ mode: "highest" }).length > 1) {
|
|
367
|
-
const setReplaceIds = (ids) => {
|
|
368
|
-
editor.setOption(AIChatPlugin, "_replaceIds", ids);
|
|
369
|
-
};
|
|
370
|
-
if (editor.getOption(AIChatPlugin, "_replaceIds").length === 0) {
|
|
371
|
-
setReplaceIds(chatNodes.map((node) => node.id));
|
|
372
|
-
}
|
|
373
|
-
const diffNodes = getDiffNodes(editor, content);
|
|
374
|
-
const replaceNodes = Array.from(
|
|
375
|
-
editor.api.nodes({
|
|
376
|
-
at: [],
|
|
377
|
-
match: (n) => ElementApi.isElement(n) && editor.getOption(AIChatPlugin, "_replaceIds").includes(n.id)
|
|
378
|
-
})
|
|
379
|
-
);
|
|
380
|
-
replaceNodes.forEach(([node, path], index) => {
|
|
381
|
-
const replaceNode = node;
|
|
382
|
-
const diffNode = diffNodes[index];
|
|
383
|
-
const isSameString = SkipSuggestionDeletes(editor, replaceNode) === SkipSuggestionDeletes(editor, diffNode);
|
|
384
|
-
const isSameSuggestion = replaceNode.suggestion?.type === diffNode.suggestion?.type;
|
|
385
|
-
if (index === replaceNodes.length - 1 && diffNodes.length > replaceNodes.length) {
|
|
386
|
-
editor.tf.replaceNodes(diffNodes.slice(index), {
|
|
387
|
-
at: path
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
if (isSameString && isSameSuggestion && node.id == diffNode.id) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
editor.tf.replaceNodes(diffNode, {
|
|
394
|
-
at: path
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
editor.getApi(BlockSelectionPlugin).blockSelection.set(diffNodes.map((node) => node.id));
|
|
398
|
-
setReplaceIds(diffNodes.map((node) => node.id));
|
|
399
|
-
} else {
|
|
400
|
-
const diffNodes = getDiffNodes(editor, content);
|
|
401
|
-
editor.tf.insertFragment(diffNodes);
|
|
402
|
-
const nodes = Array.from(
|
|
403
|
-
editor.api.nodes({
|
|
404
|
-
at: [],
|
|
405
|
-
mode: "lowest",
|
|
406
|
-
match: (n) => TextApi2.isText(n) && !!n[getTransientSuggestionKey3()]
|
|
407
|
-
})
|
|
408
|
-
);
|
|
409
|
-
const range = editor.api.nodesRange(nodes);
|
|
410
|
-
editor.tf.setSelection(range);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
var withProps = (diffNodes, chatNodes) => {
|
|
415
|
-
return diffNodes.map((node, index) => {
|
|
416
|
-
if (!ElementApi.isElement(node)) return node;
|
|
417
|
-
const originalNode = chatNodes?.[index];
|
|
418
|
-
return {
|
|
419
|
-
...node,
|
|
420
|
-
...originalNode ?? { id: nanoid() },
|
|
421
|
-
children: node.children
|
|
422
|
-
};
|
|
423
|
-
});
|
|
424
|
-
};
|
|
425
|
-
var withTransient = (diffNodes) => {
|
|
426
|
-
return diffNodes.map((node) => {
|
|
427
|
-
if (TextApi2.isText(node)) {
|
|
428
|
-
return {
|
|
429
|
-
...node,
|
|
430
|
-
[getTransientSuggestionKey3()]: true
|
|
431
|
-
};
|
|
432
|
-
} else {
|
|
433
|
-
return {
|
|
434
|
-
...node,
|
|
435
|
-
children: withTransient(node.children),
|
|
436
|
-
[getTransientSuggestionKey3()]: true
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
};
|
|
441
|
-
var withoutSuggestionAndComments = (nodes) => {
|
|
442
|
-
return nodes.map((node) => {
|
|
443
|
-
if (TextApi2.isText(node)) {
|
|
444
|
-
if (node[KEYS5.suggestion] || node[KEYS5.comment]) {
|
|
445
|
-
return {
|
|
446
|
-
text: node.text
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
return node;
|
|
450
|
-
}
|
|
451
|
-
if (ElementApi.isElement(node)) {
|
|
452
|
-
if (node[KEYS5.suggestion]) {
|
|
453
|
-
const nodeWithoutSuggestion = {};
|
|
454
|
-
Object.keys(node).forEach((key) => {
|
|
455
|
-
if (key !== KEYS5.suggestion && !key.startsWith(KEYS5.suggestion)) {
|
|
456
|
-
nodeWithoutSuggestion[key] = node[key];
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
return {
|
|
460
|
-
...nodeWithoutSuggestion,
|
|
461
|
-
children: withoutSuggestionAndComments(node.children)
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
return {
|
|
465
|
-
...node,
|
|
466
|
-
children: withoutSuggestionAndComments(node.children)
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
return node;
|
|
470
|
-
});
|
|
471
|
-
};
|
|
472
|
-
var getDiffNodes = (editor, aiContent) => {
|
|
473
|
-
const chatNodes = withoutSuggestionAndComments(
|
|
474
|
-
editor.getOption(AIChatPlugin, "chatNodes")
|
|
475
|
-
);
|
|
476
|
-
const aiNodes = withProps(deserializeMd2(editor, aiContent), chatNodes);
|
|
477
|
-
const diffNodes = withTransient(
|
|
478
|
-
diffToSuggestions(editor, chatNodes, aiNodes, {
|
|
479
|
-
ignoreProps: ["id", "listStart"]
|
|
480
|
-
})
|
|
481
|
-
);
|
|
482
|
-
return diffNodes;
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// src/react/ai-chat/utils/getLastAssistantMessage.ts
|
|
486
|
-
import { usePluginOption } from "platejs/react";
|
|
487
|
-
function getLastAssistantMessage(editor) {
|
|
488
|
-
const messages = editor.getOptions(AIChatPlugin).chat.messages;
|
|
489
|
-
return messages?.findLast((message) => message.role === "assistant");
|
|
490
|
-
}
|
|
491
|
-
function useLastAssistantMessage() {
|
|
492
|
-
const toolName = usePluginOption(AIChatPlugin, "toolName");
|
|
493
|
-
const chat = usePluginOption(AIChatPlugin, "chat");
|
|
494
|
-
if (toolName === "comment") return;
|
|
495
|
-
return chat.messages?.findLast((message) => message.role === "assistant");
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// src/react/ai-chat/utils/rejectAISuggestions.ts
|
|
499
|
-
import { getSuggestionKey as getSuggestionKey2, getTransientSuggestionKey as getTransientSuggestionKey4, rejectSuggestion } from "@platejs/suggestion";
|
|
500
|
-
import { SuggestionPlugin as SuggestionPlugin2 } from "@platejs/suggestion/react";
|
|
501
|
-
var rejectAISuggestions = (editor) => {
|
|
502
|
-
const suggestions = editor.getApi(SuggestionPlugin2).suggestion.nodes({
|
|
503
|
-
transient: true
|
|
504
|
-
});
|
|
505
|
-
suggestions.forEach(([suggestionNode]) => {
|
|
506
|
-
const suggestionData = editor.getApi(SuggestionPlugin2).suggestion.suggestionData(suggestionNode);
|
|
507
|
-
if (!suggestionData) return;
|
|
508
|
-
const description = {
|
|
509
|
-
createdAt: new Date(suggestionData.createdAt),
|
|
510
|
-
keyId: getSuggestionKey2(suggestionData.id),
|
|
511
|
-
suggestionId: suggestionData.id,
|
|
512
|
-
type: suggestionData.type,
|
|
513
|
-
userId: suggestionData.userId
|
|
514
|
-
};
|
|
515
|
-
rejectSuggestion(editor, description);
|
|
516
|
-
});
|
|
517
|
-
editor.tf.unsetNodes([getTransientSuggestionKey4()], {
|
|
518
|
-
at: [],
|
|
519
|
-
mode: "all",
|
|
520
|
-
match: (n) => !!n[getTransientSuggestionKey4()]
|
|
521
|
-
});
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
// src/react/ai-chat/utils/resetAIChat.ts
|
|
525
|
-
import { KEYS as KEYS6 } from "platejs";
|
|
526
|
-
import { getEditorPlugin as getEditorPlugin2 } from "platejs/react";
|
|
527
|
-
var resetAIChat = (editor, { undo = true } = {}) => {
|
|
528
|
-
const { api, getOptions, setOptions } = getEditorPlugin2(
|
|
529
|
-
editor,
|
|
530
|
-
{
|
|
531
|
-
key: KEYS6.aiChat
|
|
532
|
-
}
|
|
533
|
-
);
|
|
534
|
-
api.aiChat.stop();
|
|
535
|
-
const chat = getOptions().chat;
|
|
536
|
-
if (chat.messages && chat.messages.length > 0) {
|
|
537
|
-
chat.setMessages?.([]);
|
|
538
|
-
}
|
|
539
|
-
setOptions({
|
|
540
|
-
_replaceIds: [],
|
|
541
|
-
chatNodes: [],
|
|
542
|
-
mode: "insert",
|
|
543
|
-
toolName: null
|
|
544
|
-
});
|
|
545
|
-
if (undo) {
|
|
546
|
-
editor.getTransforms(AIPlugin).ai.undo();
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
// src/react/ai-chat/utils/submitAIChat.ts
|
|
551
|
-
import { isSelecting as isSelecting2 } from "@platejs/selection";
|
|
552
|
-
import { BlockSelectionPlugin as BlockSelectionPlugin2 } from "@platejs/selection/react";
|
|
553
|
-
import { KEYS as KEYS7 } from "platejs";
|
|
554
|
-
import { getEditorPlugin as getEditorPlugin3 } from "platejs/react";
|
|
555
|
-
var submitAIChat = (editor, input, {
|
|
556
|
-
mode,
|
|
557
|
-
options,
|
|
558
|
-
prompt,
|
|
559
|
-
toolName: toolNameProps
|
|
560
|
-
} = {}) => {
|
|
561
|
-
const { getOptions, setOption } = getEditorPlugin3(
|
|
562
|
-
editor,
|
|
563
|
-
{
|
|
564
|
-
key: KEYS7.aiChat
|
|
565
|
-
}
|
|
566
|
-
);
|
|
567
|
-
const { chat, toolName: toolNameOption } = getOptions();
|
|
568
|
-
const toolName = toolNameProps ?? toolNameOption ?? null;
|
|
569
|
-
if (!prompt && input?.length === 0) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (!prompt) {
|
|
573
|
-
prompt = input;
|
|
574
|
-
}
|
|
575
|
-
if (!mode) {
|
|
576
|
-
mode = isSelecting2(editor) ? "chat" : "insert";
|
|
577
|
-
}
|
|
578
|
-
if (mode === "insert") {
|
|
579
|
-
editor.getTransforms(AIPlugin).ai.undo();
|
|
580
|
-
}
|
|
581
|
-
setOption("mode", mode);
|
|
582
|
-
setOption("toolName", toolName);
|
|
583
|
-
const blocks = editor.getApi(BlockSelectionPlugin2).blockSelection.getNodes();
|
|
584
|
-
const blocksRange = editor.api.nodesRange(blocks);
|
|
585
|
-
const promptText = getEditorPrompt(editor, {
|
|
586
|
-
prompt
|
|
587
|
-
});
|
|
588
|
-
const selection = blocks.length > 0 ? blocksRange : editor.selection;
|
|
589
|
-
let chatNodes;
|
|
590
|
-
if (blocks.length > 0) {
|
|
591
|
-
chatNodes = blocks.map((block) => block[0]);
|
|
592
|
-
} else {
|
|
593
|
-
const selectionBlocks = editor.api.blocks({ mode: "highest" });
|
|
594
|
-
if (selectionBlocks.length > 1) {
|
|
595
|
-
chatNodes = selectionBlocks.map((block) => block[0]);
|
|
596
|
-
} else {
|
|
597
|
-
chatNodes = editor.api.fragment();
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
setOption("chatNodes", chatNodes);
|
|
601
|
-
setOption("chatSelection", blocks.length > 0 ? null : editor.selection);
|
|
602
|
-
const ctx = {
|
|
603
|
-
children: editor.children,
|
|
604
|
-
selection: selection ?? null,
|
|
605
|
-
toolName
|
|
606
|
-
};
|
|
607
|
-
void chat.sendMessage?.(
|
|
608
|
-
{
|
|
609
|
-
text: promptText
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
body: {
|
|
613
|
-
ctx
|
|
614
|
-
},
|
|
615
|
-
...options
|
|
616
|
-
}
|
|
617
|
-
);
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
// src/react/ai-chat/transforms/replaceSelectionAIChat.ts
|
|
621
|
-
import {
|
|
622
|
-
BlockSelectionPlugin as BlockSelectionPlugin3,
|
|
623
|
-
removeBlockSelectionNodes
|
|
624
|
-
} from "@platejs/selection/react";
|
|
625
|
-
import cloneDeep from "lodash/cloneDeep.js";
|
|
626
|
-
import {
|
|
627
|
-
KEYS as KEYS8,
|
|
628
|
-
NodeApi as NodeApi3,
|
|
629
|
-
TextApi as TextApi3
|
|
630
|
-
} from "platejs";
|
|
631
|
-
var createFormattedBlocks = ({
|
|
632
|
-
blocks,
|
|
633
|
-
format,
|
|
634
|
-
sourceBlock
|
|
635
|
-
}) => {
|
|
636
|
-
if (format === "none") return cloneDeep(blocks);
|
|
637
|
-
const [sourceNode] = sourceBlock;
|
|
638
|
-
const firstTextEntry = NodeApi3.firstText(sourceNode);
|
|
639
|
-
if (!firstTextEntry) return null;
|
|
640
|
-
const blockProps = NodeApi3.extractProps(sourceNode);
|
|
641
|
-
const textProps = NodeApi3.extractProps(firstTextEntry[0]);
|
|
642
|
-
const applyTextFormatting = (node) => {
|
|
643
|
-
if (TextApi3.isText(node)) {
|
|
644
|
-
return { ...textProps, ...node };
|
|
645
|
-
}
|
|
646
|
-
if (node.children) {
|
|
647
|
-
return {
|
|
648
|
-
...node,
|
|
649
|
-
children: node.children.map(applyTextFormatting)
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
return node;
|
|
653
|
-
};
|
|
654
|
-
return blocks.map((block, index) => {
|
|
655
|
-
if (format === "single" && index > 0) {
|
|
656
|
-
return block;
|
|
657
|
-
}
|
|
658
|
-
return applyTextFormatting({
|
|
659
|
-
...block,
|
|
660
|
-
...blockProps
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
};
|
|
664
|
-
var replaceSelectionAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
|
|
665
|
-
if (!sourceEditor || sourceEditor.api.isEmpty()) return;
|
|
666
|
-
const isBlockSelecting = editor.getOption(
|
|
667
|
-
BlockSelectionPlugin3,
|
|
668
|
-
"isSelectingSome"
|
|
669
|
-
);
|
|
670
|
-
editor.getApi({ key: KEYS8.ai }).aiChat.hide();
|
|
671
|
-
if (!isBlockSelecting) {
|
|
672
|
-
const firstBlock = editor.api.node({
|
|
673
|
-
block: true,
|
|
674
|
-
mode: "lowest"
|
|
675
|
-
});
|
|
676
|
-
if (firstBlock && editor.api.isSelected(firstBlock[1], { contains: true }) && format !== "none") {
|
|
677
|
-
const formattedBlocks2 = createFormattedBlocks({
|
|
678
|
-
blocks: cloneDeep(sourceEditor.children),
|
|
679
|
-
format,
|
|
680
|
-
sourceBlock: firstBlock
|
|
681
|
-
});
|
|
682
|
-
if (!formattedBlocks2) return;
|
|
683
|
-
if (firstBlock[0].type === KEYS8.codeLine && sourceEditor.children[0].type === KEYS8.codeBlock && sourceEditor.children.length === 1) {
|
|
684
|
-
editor.tf.insertFragment(formattedBlocks2[0].children);
|
|
685
|
-
} else {
|
|
686
|
-
editor.tf.insertFragment(formattedBlocks2);
|
|
687
|
-
}
|
|
688
|
-
editor.tf.focus();
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
editor.tf.insertFragment(sourceEditor.children);
|
|
692
|
-
editor.tf.focus();
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
const blockSelectionApi = editor.getApi(BlockSelectionPlugin3).blockSelection;
|
|
696
|
-
const selectedBlocks = blockSelectionApi.getNodes();
|
|
697
|
-
if (selectedBlocks.length === 0) return;
|
|
698
|
-
if (format === "none" || format === "single" && selectedBlocks.length > 1) {
|
|
699
|
-
editor.tf.withoutNormalizing(() => {
|
|
700
|
-
removeBlockSelectionNodes(editor);
|
|
701
|
-
editor.tf.withNewBatch(() => {
|
|
702
|
-
editor.getTransforms(BlockSelectionPlugin3).blockSelection.insertBlocksAndSelect(
|
|
703
|
-
cloneDeep(sourceEditor.children),
|
|
704
|
-
{
|
|
705
|
-
at: selectedBlocks[0][1]
|
|
706
|
-
}
|
|
707
|
-
);
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
editor.getApi(BlockSelectionPlugin3).blockSelection.focus();
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
const [, firstBlockPath] = selectedBlocks[0];
|
|
714
|
-
const formattedBlocks = createFormattedBlocks({
|
|
715
|
-
blocks: cloneDeep(sourceEditor.children),
|
|
716
|
-
format,
|
|
717
|
-
sourceBlock: selectedBlocks[0]
|
|
718
|
-
});
|
|
719
|
-
if (!formattedBlocks) return;
|
|
720
|
-
editor.tf.withoutNormalizing(() => {
|
|
721
|
-
removeBlockSelectionNodes(editor);
|
|
722
|
-
editor.tf.withNewBatch(() => {
|
|
723
|
-
editor.getTransforms(BlockSelectionPlugin3).blockSelection.insertBlocksAndSelect(formattedBlocks, {
|
|
724
|
-
at: firstBlockPath
|
|
725
|
-
});
|
|
726
|
-
});
|
|
727
|
-
});
|
|
728
|
-
editor.getApi(BlockSelectionPlugin3).blockSelection.focus();
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
// src/react/ai-chat/transforms/insertBelowAIChat.ts
|
|
732
|
-
var insertBelowAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
|
|
733
|
-
const { toolName } = editor.getOptions(AIChatPlugin);
|
|
734
|
-
if (toolName === "generate")
|
|
735
|
-
return insertBelowGenerate(editor, sourceEditor, { format });
|
|
736
|
-
const selectedBlocks = editor.getApi(BlockSelectionPlugin4).blockSelection.getNodes();
|
|
737
|
-
const selectedIds = editor.getOptions(BlockSelectionPlugin4).selectedIds;
|
|
738
|
-
editor.getTransforms(AIPlugin).ai.undo();
|
|
739
|
-
const insertBlocksAndSelect = editor.getTransforms(BlockSelectionPlugin4).blockSelection.insertBlocksAndSelect;
|
|
740
|
-
if (!selectedIds || selectedIds.size === 0) return;
|
|
741
|
-
const lastBlock = selectedBlocks.at(-1);
|
|
742
|
-
if (!lastBlock) return;
|
|
743
|
-
const nextPath = PathApi.next(lastBlock[1]);
|
|
744
|
-
const nodes = selectedBlocks.map((block) => block[0]);
|
|
745
|
-
insertBlocksAndSelect(nodes, {
|
|
746
|
-
at: nextPath,
|
|
747
|
-
insertedCallback: () => {
|
|
748
|
-
withAIBatch(editor, () => {
|
|
749
|
-
acceptAISuggestions(editor);
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
editor.getApi(AIChatPlugin).aiChat.hide({ focus: false });
|
|
754
|
-
};
|
|
755
|
-
var insertBelowGenerate = (editor, sourceEditor, { format = "single" } = {}) => {
|
|
756
|
-
if (!sourceEditor || sourceEditor.api.isEmpty()) return;
|
|
757
|
-
const isBlockSelecting = editor.getOption(
|
|
758
|
-
BlockSelectionPlugin4,
|
|
759
|
-
"isSelectingSome"
|
|
760
|
-
);
|
|
761
|
-
editor.getApi({ key: KEYS9.ai }).aiChat.hide();
|
|
762
|
-
const insertBlocksAndSelect = editor.getTransforms(BlockSelectionPlugin4).blockSelection.insertBlocksAndSelect;
|
|
763
|
-
if (isBlockSelecting) {
|
|
764
|
-
const selectedBlocks = editor.getApi(BlockSelectionPlugin4).blockSelection.getNodes();
|
|
765
|
-
const selectedIds = editor.getOptions(BlockSelectionPlugin4).selectedIds;
|
|
766
|
-
if (!selectedIds || selectedIds.size === 0) return;
|
|
767
|
-
const lastBlock = selectedBlocks.at(-1);
|
|
768
|
-
if (!lastBlock) return;
|
|
769
|
-
const nextPath = PathApi.next(lastBlock[1]);
|
|
770
|
-
if (format === "none") {
|
|
771
|
-
insertBlocksAndSelect(cloneDeep2(sourceEditor.children), {
|
|
772
|
-
at: nextPath
|
|
773
|
-
});
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
const formattedBlocks = createFormattedBlocks({
|
|
777
|
-
blocks: cloneDeep2(sourceEditor.children),
|
|
778
|
-
format,
|
|
779
|
-
sourceBlock: lastBlock
|
|
780
|
-
});
|
|
781
|
-
if (!formattedBlocks) return;
|
|
782
|
-
insertBlocksAndSelect(formattedBlocks, {
|
|
783
|
-
at: nextPath
|
|
784
|
-
});
|
|
785
|
-
} else {
|
|
786
|
-
const [, end] = RangeApi.edges(editor.selection);
|
|
787
|
-
const endPath = [end.path[0]];
|
|
788
|
-
const currentBlock = editor.api.node({
|
|
789
|
-
at: endPath,
|
|
790
|
-
block: true,
|
|
791
|
-
mode: "lowest"
|
|
792
|
-
});
|
|
793
|
-
if (!currentBlock) return;
|
|
794
|
-
if (format === "none") {
|
|
795
|
-
insertBlocksAndSelect(cloneDeep2(sourceEditor.children), {
|
|
796
|
-
at: PathApi.next(endPath)
|
|
797
|
-
});
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
const formattedBlocks = createFormattedBlocks({
|
|
801
|
-
blocks: cloneDeep2(sourceEditor.children),
|
|
802
|
-
format,
|
|
803
|
-
sourceBlock: currentBlock
|
|
804
|
-
});
|
|
805
|
-
if (!formattedBlocks) return;
|
|
806
|
-
insertBlocksAndSelect(formattedBlocks, {
|
|
807
|
-
at: PathApi.next(endPath)
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
// src/react/ai-chat/transforms/removeAnchorAIChat.ts
|
|
813
|
-
import {
|
|
814
|
-
ElementApi as ElementApi2,
|
|
815
|
-
getPluginType as getPluginType2,
|
|
816
|
-
KEYS as KEYS10
|
|
817
|
-
} from "platejs";
|
|
818
|
-
var removeAnchorAIChat = (editor, options) => {
|
|
819
|
-
editor.tf.withoutSaving(() => {
|
|
820
|
-
editor.tf.removeNodes({
|
|
821
|
-
at: [],
|
|
822
|
-
match: (n) => ElementApi2.isElement(n) && n.type === getPluginType2(editor, KEYS10.aiChat),
|
|
823
|
-
...options
|
|
824
|
-
});
|
|
825
|
-
});
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
// src/react/ai-chat/withAIChat.ts
|
|
829
|
-
import { ElementApi as ElementApi3, KEYS as KEYS11 } from "platejs";
|
|
830
|
-
var withAIChat = ({
|
|
831
|
-
api,
|
|
832
|
-
editor,
|
|
833
|
-
getOptions,
|
|
834
|
-
tf: { apply, insertText, normalizeNode },
|
|
835
|
-
type
|
|
836
|
-
}) => {
|
|
837
|
-
const tf = editor.getTransforms(AIPlugin);
|
|
838
|
-
const matchesTrigger = (text) => {
|
|
839
|
-
const { trigger } = getOptions();
|
|
840
|
-
if (trigger instanceof RegExp) {
|
|
841
|
-
return trigger.test(text);
|
|
842
|
-
}
|
|
843
|
-
if (Array.isArray(trigger)) {
|
|
844
|
-
return trigger.includes(text);
|
|
845
|
-
}
|
|
846
|
-
return text === trigger;
|
|
847
|
-
};
|
|
848
|
-
return {
|
|
849
|
-
transforms: {
|
|
850
|
-
insertText(text, options) {
|
|
851
|
-
const { triggerPreviousCharPattern, triggerQuery } = getOptions();
|
|
852
|
-
const fn = () => {
|
|
853
|
-
if (!editor.selection || !matchesTrigger(text) || triggerQuery && !triggerQuery(editor)) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
const previousChar = editor.api.string(
|
|
857
|
-
editor.api.range("before", editor.selection)
|
|
858
|
-
);
|
|
859
|
-
const matchesPreviousCharPattern = triggerPreviousCharPattern?.test(previousChar);
|
|
860
|
-
if (!matchesPreviousCharPattern) return;
|
|
861
|
-
const nodeEntry = editor.api.block({ highest: true });
|
|
862
|
-
if (!nodeEntry || !editor.api.isEmpty(nodeEntry[0])) return;
|
|
863
|
-
api.aiChat.show();
|
|
864
|
-
return true;
|
|
865
|
-
};
|
|
866
|
-
if (fn()) return;
|
|
867
|
-
return insertText(text, options);
|
|
868
|
-
},
|
|
869
|
-
normalizeNode(entry) {
|
|
870
|
-
const [node, path] = entry;
|
|
871
|
-
if (node[KEYS11.ai] && !getOptions().open) {
|
|
872
|
-
tf.ai.removeMarks({ at: path });
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
if (ElementApi3.isElement(node) && node.type === type && !getOptions().open) {
|
|
876
|
-
editor.getTransforms(AIChatPlugin).aiChat.removeAnchor({ at: path });
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
return normalizeNode(entry);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
// src/react/ai-chat/AIChatPlugin.ts
|
|
886
|
-
var AIChatPlugin = createTPlatePlugin({
|
|
887
|
-
key: KEYS12.aiChat,
|
|
888
|
-
dependencies: ["ai"],
|
|
889
|
-
node: {
|
|
890
|
-
isElement: true
|
|
891
|
-
},
|
|
892
|
-
options: {
|
|
893
|
-
_blockChunks: "",
|
|
894
|
-
_blockPath: null,
|
|
895
|
-
_mdxName: null,
|
|
896
|
-
_replaceIds: [],
|
|
897
|
-
aiEditor: null,
|
|
898
|
-
chat: { messages: [] },
|
|
899
|
-
chatNodes: [],
|
|
900
|
-
chatSelection: null,
|
|
901
|
-
experimental_lastTextId: null,
|
|
902
|
-
mode: "insert",
|
|
903
|
-
open: false,
|
|
904
|
-
streaming: false,
|
|
905
|
-
toolName: null,
|
|
906
|
-
trigger: " ",
|
|
907
|
-
triggerPreviousCharPattern: /^\s?$/
|
|
908
|
-
}
|
|
909
|
-
}).overrideEditor(withAIChat).extendApi(({ editor, getOption, getOptions, setOption, type }) => {
|
|
910
|
-
return {
|
|
911
|
-
reset: bindFirst2(resetAIChat, editor),
|
|
912
|
-
submit: bindFirst2(submitAIChat, editor),
|
|
913
|
-
node: (options = {}) => {
|
|
914
|
-
const { anchor = false, streaming = false, ...rest } = options;
|
|
915
|
-
if (anchor) {
|
|
916
|
-
return editor.api.node({
|
|
917
|
-
at: [],
|
|
918
|
-
match: (n) => ElementApi4.isElement(n) && n.type === type,
|
|
919
|
-
...rest
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
if (streaming) {
|
|
923
|
-
if (!getOption("streaming")) return;
|
|
924
|
-
const path = getOption("_blockPath");
|
|
925
|
-
if (!path) return;
|
|
926
|
-
return editor.api.node({
|
|
927
|
-
at: path,
|
|
928
|
-
mode: "lowest",
|
|
929
|
-
reverse: true,
|
|
930
|
-
match: (t) => !!t[getPluginType3(editor, KEYS12.ai)],
|
|
931
|
-
...rest
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
return editor.api.node({
|
|
935
|
-
match: (n) => n[getPluginType3(editor, KEYS12.ai)],
|
|
936
|
-
...rest
|
|
937
|
-
});
|
|
938
|
-
},
|
|
939
|
-
reload: () => {
|
|
940
|
-
const { chat, chatNodes, chatSelection } = getOptions();
|
|
941
|
-
editor.getTransforms(AIPlugin).ai.undo();
|
|
942
|
-
if (chatSelection) {
|
|
943
|
-
editor.tf.setSelection(chatSelection);
|
|
944
|
-
} else {
|
|
945
|
-
editor.getApi(BlockSelectionPlugin5).blockSelection.set(chatNodes.map((node) => node.id));
|
|
946
|
-
}
|
|
947
|
-
const blocks = editor.getApi(BlockSelectionPlugin5).blockSelection.getNodes();
|
|
948
|
-
const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
|
|
949
|
-
const ctx = {
|
|
950
|
-
children: editor.children,
|
|
951
|
-
selection: selection ?? null,
|
|
952
|
-
toolName: getOption("toolName")
|
|
953
|
-
};
|
|
954
|
-
void chat.regenerate?.({
|
|
955
|
-
body: {
|
|
956
|
-
ctx
|
|
957
|
-
}
|
|
958
|
-
});
|
|
959
|
-
},
|
|
960
|
-
stop: () => {
|
|
961
|
-
setOption("streaming", false);
|
|
962
|
-
getOptions().chat.stop?.();
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
}).extendApi(({ api, editor, getOptions, setOption, tf, type }) => ({
|
|
966
|
-
hide: ({
|
|
967
|
-
focus = true,
|
|
968
|
-
undo = true
|
|
969
|
-
} = {}) => {
|
|
970
|
-
api.aiChat.reset({ undo });
|
|
971
|
-
setOption("open", false);
|
|
972
|
-
if (focus) {
|
|
973
|
-
if (editor.getOption(BlockSelectionPlugin5, "isSelectingSome")) {
|
|
974
|
-
editor.getApi(BlockSelectionPlugin5).blockSelection.focus();
|
|
975
|
-
} else {
|
|
976
|
-
editor.tf.focus();
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
const lastBatch = editor.history.undos.at(-1);
|
|
980
|
-
if (lastBatch?.ai) {
|
|
981
|
-
delete lastBatch.ai;
|
|
982
|
-
}
|
|
983
|
-
tf.aiChat.removeAnchor();
|
|
984
|
-
},
|
|
985
|
-
show: () => {
|
|
986
|
-
api.aiChat.reset();
|
|
987
|
-
setOption("toolName", null);
|
|
988
|
-
getOptions().chat.setMessages?.([]);
|
|
989
|
-
setOption("open", true);
|
|
990
|
-
}
|
|
991
|
-
})).extendTransforms(({ editor }) => ({
|
|
992
|
-
accept: bindFirst2(acceptAIChat, editor),
|
|
993
|
-
insertBelow: bindFirst2(insertBelowAIChat, editor),
|
|
994
|
-
removeAnchor: bindFirst2(removeAnchorAIChat, editor),
|
|
995
|
-
replaceSelection: bindFirst2(replaceSelectionAIChat, editor)
|
|
996
|
-
}));
|
|
997
|
-
|
|
998
|
-
// src/react/ai-chat/hooks/useAIChatEditor.ts
|
|
999
|
-
import { useEffect, useMemo } from "react";
|
|
1000
|
-
import { MarkdownPlugin } from "@platejs/markdown";
|
|
1001
|
-
import { useEditorPlugin } from "platejs/react";
|
|
1002
|
-
var useAIChatEditor = (editor, content, { parser } = {}) => {
|
|
1003
|
-
const { setOption } = useEditorPlugin(AIChatPlugin);
|
|
1004
|
-
editor.children = useMemo(
|
|
1005
|
-
() => {
|
|
1006
|
-
return editor.getApi(MarkdownPlugin).markdown.deserialize(content, {
|
|
1007
|
-
memoize: true,
|
|
1008
|
-
parser
|
|
1009
|
-
});
|
|
1010
|
-
},
|
|
1011
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1012
|
-
[content]
|
|
1013
|
-
);
|
|
1014
|
-
useEffect(() => {
|
|
1015
|
-
setOption("aiEditor", editor);
|
|
1016
|
-
}, [editor, setOption]);
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
// src/react/ai-chat/hooks/useChatChunk.ts
|
|
1020
|
-
import { useEffect as useEffect2, useRef } from "react";
|
|
1021
|
-
import { KEYS as KEYS13 } from "platejs";
|
|
1022
|
-
import { usePluginOption as usePluginOption2 } from "platejs/react";
|
|
1023
|
-
var useChatChunk = ({
|
|
1024
|
-
onChunk,
|
|
1025
|
-
onFinish
|
|
1026
|
-
}) => {
|
|
1027
|
-
const { status } = usePluginOption2(
|
|
1028
|
-
{ key: KEYS13.aiChat },
|
|
1029
|
-
"chat"
|
|
1030
|
-
);
|
|
1031
|
-
const isLoading = status === "streaming" || status === "submitted";
|
|
1032
|
-
const content = useLastAssistantMessage()?.parts.find(
|
|
1033
|
-
(part) => part.type === "text"
|
|
1034
|
-
)?.text;
|
|
1035
|
-
const insertedTextRef = useRef("");
|
|
1036
|
-
const prevIsLoadingRef = useRef(isLoading);
|
|
1037
|
-
useEffect2(() => {
|
|
1038
|
-
if (!isLoading) {
|
|
1039
|
-
insertedTextRef.current = "";
|
|
1040
|
-
}
|
|
1041
|
-
if (prevIsLoadingRef.current && !isLoading) {
|
|
1042
|
-
onFinish?.({ content: content ?? "" });
|
|
1043
|
-
}
|
|
1044
|
-
prevIsLoadingRef.current = isLoading;
|
|
1045
|
-
}, [isLoading]);
|
|
1046
|
-
useEffect2(() => {
|
|
1047
|
-
if (!content) {
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
const chunk = content.slice(insertedTextRef.current.length);
|
|
1051
|
-
const nodes = [];
|
|
1052
|
-
if (chunk) {
|
|
1053
|
-
const isFirst = insertedTextRef.current === "";
|
|
1054
|
-
nodes.push({ text: chunk });
|
|
1055
|
-
onChunk({
|
|
1056
|
-
chunk,
|
|
1057
|
-
isFirst,
|
|
1058
|
-
nodes,
|
|
1059
|
-
text: content
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
insertedTextRef.current = content;
|
|
1063
|
-
}, [content]);
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
// src/react/ai-chat/hooks/useEditorChat.ts
|
|
1067
|
-
import { useEffect as useEffect3 } from "react";
|
|
1068
|
-
import { BlockSelectionPlugin as BlockSelectionPlugin6 } from "@platejs/selection/react";
|
|
1069
|
-
import { useEditorPlugin as useEditorPlugin2, usePluginOption as usePluginOption3 } from "platejs/react";
|
|
1070
|
-
var useEditorChat = ({
|
|
1071
|
-
chat,
|
|
1072
|
-
onOpenBlockSelection,
|
|
1073
|
-
onOpenChange,
|
|
1074
|
-
onOpenCursor,
|
|
1075
|
-
onOpenSelection
|
|
1076
|
-
}) => {
|
|
1077
|
-
const { editor } = useEditorPlugin2(AIChatPlugin);
|
|
1078
|
-
const open = usePluginOption3(AIChatPlugin, "open");
|
|
1079
|
-
useEffect3(() => {
|
|
1080
|
-
onOpenChange?.(open);
|
|
1081
|
-
if (open) {
|
|
1082
|
-
if (onOpenBlockSelection) {
|
|
1083
|
-
const blockSelectionApi = editor.getApi(BlockSelectionPlugin6).blockSelection;
|
|
1084
|
-
const isBlockSelecting = editor.getOption(
|
|
1085
|
-
BlockSelectionPlugin6,
|
|
1086
|
-
"isSelectingSome"
|
|
1087
|
-
);
|
|
1088
|
-
if (isBlockSelecting) {
|
|
1089
|
-
onOpenBlockSelection(blockSelectionApi.getNodes());
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
if (onOpenCursor && editor.api.isCollapsed()) {
|
|
1094
|
-
onOpenCursor();
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
if (onOpenSelection && editor.api.isExpanded()) {
|
|
1098
|
-
onOpenSelection();
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
}, [open]);
|
|
1103
|
-
};
|
|
1104
|
-
|
|
1105
|
-
// src/react/ai-chat/streaming/streamDeserializeInlineMd.ts
|
|
1106
|
-
import { MarkdownPlugin as MarkdownPlugin2 } from "@platejs/markdown";
|
|
1107
|
-
var streamDeserializeInlineMd = (editor, text, options) => {
|
|
1108
|
-
return editor.getApi(MarkdownPlugin2).markdown.deserializeInline(text, options);
|
|
1109
|
-
};
|
|
1110
|
-
|
|
1111
|
-
// src/react/ai-chat/streaming/streamDeserializeMd.ts
|
|
1112
|
-
import { MarkdownPlugin as MarkdownPlugin3 } from "@platejs/markdown";
|
|
1113
|
-
import { getPluginType as getPluginType4, KEYS as KEYS15, TextApi as TextApi4 } from "platejs";
|
|
1114
|
-
|
|
1115
|
-
// src/react/ai-chat/streaming/utils/utils.ts
|
|
1116
|
-
var getChunkTrimmed = (chunk, {
|
|
1117
|
-
direction = "right"
|
|
1118
|
-
} = {}) => {
|
|
1119
|
-
const str = direction === "right" ? chunk.trimEnd() : chunk.trimStart();
|
|
1120
|
-
if (direction === "right") {
|
|
1121
|
-
return chunk.slice(str.length);
|
|
1122
|
-
} else {
|
|
1123
|
-
return chunk.slice(0, chunk.length - str.length);
|
|
1124
|
-
}
|
|
1125
|
-
};
|
|
1126
|
-
function isCompleteCodeBlock(str) {
|
|
1127
|
-
const trimmed = str.trim();
|
|
1128
|
-
const startsWithCodeBlock = trimmed.startsWith("```");
|
|
1129
|
-
const endsWithCodeBlock = trimmed.endsWith("```");
|
|
1130
|
-
return startsWithCodeBlock && endsWithCodeBlock;
|
|
1131
|
-
}
|
|
1132
|
-
function isCompleteMath(str) {
|
|
1133
|
-
const trimmed = str.trim();
|
|
1134
|
-
const startsWithMath = trimmed.startsWith("$$");
|
|
1135
|
-
const endsWithMath = trimmed.endsWith("$$");
|
|
1136
|
-
return startsWithMath && endsWithMath;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// src/react/ai-chat/streaming/utils/escapeInput.ts
|
|
1140
|
-
var escapeInput = (data) => {
|
|
1141
|
-
let res = data;
|
|
1142
|
-
if (data.startsWith("$$") && !data.startsWith("$$\n") && !isCompleteMath(data)) {
|
|
1143
|
-
res = data.replace("$$", String.raw`\$\$`);
|
|
1144
|
-
}
|
|
1145
|
-
return res;
|
|
1146
|
-
};
|
|
1147
|
-
|
|
1148
|
-
// src/react/ai-chat/streaming/utils/getListNode.ts
|
|
1149
|
-
var getListNode = (editor, node) => {
|
|
1150
|
-
if (node.listStyleType && node.listStart) {
|
|
1151
|
-
const previousNode = editor.api.previous({
|
|
1152
|
-
at: editor.selection?.focus
|
|
1153
|
-
})?.[0];
|
|
1154
|
-
if (previousNode?.listStyleType && previousNode?.listStart) {
|
|
1155
|
-
return node;
|
|
1156
|
-
} else {
|
|
1157
|
-
if (node.listStart === 1) return node;
|
|
1158
|
-
return {
|
|
1159
|
-
...node,
|
|
1160
|
-
listRestartPolite: node.listStart
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
return node;
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
// src/react/ai-chat/streaming/utils/isSameNode.ts
|
|
1168
|
-
import { isDefined, KEYS as KEYS14 } from "platejs";
|
|
1169
|
-
var LIST_STYLE_TYPE = "listStyleType";
|
|
1170
|
-
var isSameNode = (editor, node1, node2) => {
|
|
1171
|
-
if (node1.type !== editor.getType(KEYS14.p) || node2.type !== editor.getType(KEYS14.p))
|
|
1172
|
-
return node1.type === node2.type;
|
|
1173
|
-
if (isDefined(node1[LIST_STYLE_TYPE]) || isDefined(node2[LIST_STYLE_TYPE])) {
|
|
1174
|
-
return node1[LIST_STYLE_TYPE] === node2[LIST_STYLE_TYPE];
|
|
1175
|
-
}
|
|
1176
|
-
return node1.type === node2.type;
|
|
1177
|
-
};
|
|
1178
|
-
|
|
1179
|
-
// src/react/ai-chat/streaming/utils/nodesWithProps.ts
|
|
1180
|
-
import { ElementApi as ElementApi5 } from "platejs";
|
|
1181
|
-
var nodesWithProps = (editor, nodes, options) => {
|
|
1182
|
-
return nodes.map((node) => {
|
|
1183
|
-
if (ElementApi5.isElement(node)) {
|
|
1184
|
-
return {
|
|
1185
|
-
...getListNode(editor, node),
|
|
1186
|
-
...options.elementProps,
|
|
1187
|
-
children: nodesWithProps(editor, node.children, options)
|
|
1188
|
-
};
|
|
1189
|
-
} else {
|
|
1190
|
-
return {
|
|
1191
|
-
...options.textProps,
|
|
1192
|
-
...node,
|
|
1193
|
-
text: node.text
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
});
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
// src/react/ai-chat/streaming/streamDeserializeMd.ts
|
|
1200
|
-
var statMdxTagRegex = /<([A-Za-z][A-Za-z0-9._:-]*)(?:\s[^>]*?)?(?<!\/)>/;
|
|
1201
|
-
var streamDeserializeMd = (editor, data, options) => {
|
|
1202
|
-
const input = escapeInput(data);
|
|
1203
|
-
const value = withoutDeserializeInMdx(editor, input);
|
|
1204
|
-
if (Array.isArray(value)) return value;
|
|
1205
|
-
let blocks = [];
|
|
1206
|
-
blocks = editor.getApi(MarkdownPlugin3).markdown.deserialize(input, {
|
|
1207
|
-
...options,
|
|
1208
|
-
preserveEmptyParagraphs: false
|
|
1209
|
-
});
|
|
1210
|
-
const trimmedData = getChunkTrimmed(data);
|
|
1211
|
-
const lastBlock = blocks.at(-1);
|
|
1212
|
-
const addNewLine = trimmedData === "\n\n";
|
|
1213
|
-
const unshiftNewLine = getChunkTrimmed(data, { direction: "left" }) === "\n\n";
|
|
1214
|
-
const isCodeBlockOrTable = lastBlock?.type === "code_block" || lastBlock?.type === "table";
|
|
1215
|
-
let result = blocks;
|
|
1216
|
-
if (lastBlock && !isCodeBlockOrTable && trimmedData.length > 0 && !addNewLine) {
|
|
1217
|
-
const textNode = [
|
|
1218
|
-
{
|
|
1219
|
-
text: trimmedData
|
|
1220
|
-
}
|
|
1221
|
-
];
|
|
1222
|
-
const lastChild = lastBlock.children.at(-1);
|
|
1223
|
-
if (lastChild && TextApi4.isText(lastChild) && Object.keys(lastChild).length === 1) {
|
|
1224
|
-
lastBlock.children.pop();
|
|
1225
|
-
const textNode2 = [
|
|
1226
|
-
{
|
|
1227
|
-
text: lastChild.text + trimmedData
|
|
1228
|
-
}
|
|
1229
|
-
];
|
|
1230
|
-
lastBlock.children.push(...textNode2);
|
|
1231
|
-
} else {
|
|
1232
|
-
lastBlock.children.push(...textNode);
|
|
1233
|
-
}
|
|
1234
|
-
result = [...blocks.slice(0, -1), lastBlock];
|
|
1235
|
-
}
|
|
1236
|
-
if (addNewLine && !isCodeBlockOrTable) {
|
|
1237
|
-
result.push({
|
|
1238
|
-
children: [{ text: "" }],
|
|
1239
|
-
type: KEYS15.p
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
if (unshiftNewLine && !isCodeBlockOrTable) {
|
|
1243
|
-
result.unshift({
|
|
1244
|
-
children: [{ text: "" }],
|
|
1245
|
-
type: KEYS15.p
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
return result;
|
|
1249
|
-
};
|
|
1250
|
-
var withoutDeserializeInMdx = (editor, input) => {
|
|
1251
|
-
const mdxName = editor.getOption(AIChatPlugin, "_mdxName");
|
|
1252
|
-
if (mdxName) {
|
|
1253
|
-
const isMdxEnd = input.includes(`</${mdxName}>`);
|
|
1254
|
-
if (isMdxEnd) {
|
|
1255
|
-
editor.setOption(AIChatPlugin, "_mdxName", null);
|
|
1256
|
-
return false;
|
|
1257
|
-
} else {
|
|
1258
|
-
return [
|
|
1259
|
-
{
|
|
1260
|
-
children: [
|
|
1261
|
-
{
|
|
1262
|
-
text: input
|
|
1263
|
-
}
|
|
1264
|
-
],
|
|
1265
|
-
type: getPluginType4(editor, KEYS15.p)
|
|
1266
|
-
}
|
|
1267
|
-
];
|
|
1268
|
-
}
|
|
1269
|
-
} else {
|
|
1270
|
-
const newMdxName = statMdxTagRegex.exec(input)?.[1];
|
|
1271
|
-
if (input.startsWith(`<${newMdxName}`)) {
|
|
1272
|
-
editor.setOption(AIChatPlugin, "_mdxName", newMdxName ?? null);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
};
|
|
1276
|
-
|
|
1277
|
-
// src/react/ai-chat/streaming/streamInsertChunk.ts
|
|
1278
|
-
import {
|
|
1279
|
-
getPluginType as getPluginType5,
|
|
1280
|
-
KEYS as KEYS17,
|
|
1281
|
-
NodeApi as NodeApi4,
|
|
1282
|
-
PathApi as PathApi2
|
|
1283
|
-
} from "platejs";
|
|
1284
|
-
|
|
1285
|
-
// src/react/ai-chat/streaming/streamSerializeMd.ts
|
|
1286
|
-
import { MarkdownPlugin as MarkdownPlugin4 } from "@platejs/markdown";
|
|
1287
|
-
import {
|
|
1288
|
-
ElementApi as ElementApi6,
|
|
1289
|
-
getPluginKey,
|
|
1290
|
-
KEYS as KEYS16,
|
|
1291
|
-
TextApi as TextApi5
|
|
1292
|
-
} from "platejs";
|
|
1293
|
-
var trimEndHeading = (editor, value) => {
|
|
1294
|
-
const headingKeys = /* @__PURE__ */ new Set([
|
|
1295
|
-
KEYS16.h1,
|
|
1296
|
-
KEYS16.h2,
|
|
1297
|
-
KEYS16.h3,
|
|
1298
|
-
KEYS16.h4,
|
|
1299
|
-
KEYS16.h5,
|
|
1300
|
-
KEYS16.h6
|
|
1301
|
-
]);
|
|
1302
|
-
const lastBlock = value.at(-1);
|
|
1303
|
-
if (lastBlock && headingKeys.has(
|
|
1304
|
-
getPluginKey(editor, lastBlock.type) ?? lastBlock.type
|
|
1305
|
-
) && ElementApi6.isElement(lastBlock)) {
|
|
1306
|
-
const lastTextNode = lastBlock.children.at(-1);
|
|
1307
|
-
if (TextApi5.isText(lastTextNode)) {
|
|
1308
|
-
const trimmedText = getChunkTrimmed(lastTextNode?.text);
|
|
1309
|
-
const newChildren = [
|
|
1310
|
-
...lastBlock.children.slice(0, -1),
|
|
1311
|
-
{ text: lastTextNode.text.trimEnd() }
|
|
1312
|
-
];
|
|
1313
|
-
const newLastBlock = {
|
|
1314
|
-
...lastBlock,
|
|
1315
|
-
children: newChildren
|
|
1316
|
-
};
|
|
1317
|
-
return {
|
|
1318
|
-
trimmedText,
|
|
1319
|
-
value: [...value.slice(0, -1), newLastBlock]
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
return { trimmedText: "", value };
|
|
1324
|
-
};
|
|
1325
|
-
var streamSerializeMd = (editor, options, chunk) => {
|
|
1326
|
-
const { value: optionsValue, ...restOptions } = options;
|
|
1327
|
-
const { value } = trimEndHeading(editor, optionsValue ?? editor.children);
|
|
1328
|
-
let result = "";
|
|
1329
|
-
result = editor.getApi(MarkdownPlugin4).markdown.serialize({
|
|
1330
|
-
value,
|
|
1331
|
-
...restOptions
|
|
1332
|
-
});
|
|
1333
|
-
const trimmedChunk = getChunkTrimmed(chunk);
|
|
1334
|
-
if (isCompleteCodeBlock(result) && !chunk.endsWith("```")) {
|
|
1335
|
-
result = result.trimEnd().slice(0, -3) + trimmedChunk;
|
|
1336
|
-
}
|
|
1337
|
-
if (isCompleteMath(result) && !chunk.endsWith("$$")) {
|
|
1338
|
-
result = result.trimEnd().slice(0, -3) + trimmedChunk;
|
|
1339
|
-
}
|
|
1340
|
-
result = result.replace(/ /g, " ");
|
|
1341
|
-
result = result.replace(/​/g, " ");
|
|
1342
|
-
result = result.replace(/\u200B/g, "");
|
|
1343
|
-
if (trimmedChunk !== "\n\n") {
|
|
1344
|
-
result = result.trimEnd() + trimmedChunk;
|
|
1345
|
-
}
|
|
1346
|
-
if (chunk.endsWith("\n\n")) {
|
|
1347
|
-
if (result === "\n") {
|
|
1348
|
-
result = "";
|
|
1349
|
-
} else if (result.endsWith("\n\n")) {
|
|
1350
|
-
result = result.slice(0, -1);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
result = result.replace(/\\([\\`*_{}\\[\]()#+\-\\.!~<>|$])/g, "$1");
|
|
1354
|
-
return result;
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
// src/react/ai-chat/streaming/streamInsertChunk.ts
|
|
1358
|
-
var getNextPath = (path, length) => {
|
|
1359
|
-
let result = path;
|
|
1360
|
-
for (let i = 0; i < length; i++) {
|
|
1361
|
-
result = PathApi2.next(result);
|
|
1362
|
-
}
|
|
1363
|
-
return result;
|
|
1364
|
-
};
|
|
1365
|
-
function streamInsertChunk(editor, chunk, options = {}) {
|
|
1366
|
-
const { _blockChunks, _blockPath } = editor.getOptions(AIChatPlugin);
|
|
1367
|
-
if (_blockPath === null) {
|
|
1368
|
-
const blocks = streamDeserializeMd(editor, chunk);
|
|
1369
|
-
const path = getCurrentBlockPath(editor);
|
|
1370
|
-
const startBlock = editor.api.node(path)[0];
|
|
1371
|
-
const startInEmptyParagraph = NodeApi4.string(startBlock).length === 0 && startBlock.type === getPluginType5(editor, KEYS17.p);
|
|
1372
|
-
if (startInEmptyParagraph) {
|
|
1373
|
-
editor.tf.removeNodes({ at: path });
|
|
1374
|
-
}
|
|
1375
|
-
if (blocks.length > 0) {
|
|
1376
|
-
editor.tf.insertNodes(nodesWithProps(editor, [blocks[0]], options), {
|
|
1377
|
-
at: path,
|
|
1378
|
-
nextBlock: !startInEmptyParagraph,
|
|
1379
|
-
select: true
|
|
1380
|
-
});
|
|
1381
|
-
editor.setOption(AIChatPlugin, "_blockPath", getCurrentBlockPath(editor));
|
|
1382
|
-
editor.setOption(AIChatPlugin, "_blockChunks", chunk);
|
|
1383
|
-
if (blocks.length > 1) {
|
|
1384
|
-
const nextBlocks = blocks.slice(1);
|
|
1385
|
-
const nextPath = getCurrentBlockPath(editor);
|
|
1386
|
-
editor.tf.insertNodes(nodesWithProps(editor, nextBlocks, options), {
|
|
1387
|
-
at: nextPath,
|
|
1388
|
-
nextBlock: true,
|
|
1389
|
-
select: true
|
|
1390
|
-
});
|
|
1391
|
-
const lastBlock = editor.api.node(
|
|
1392
|
-
getNextPath(nextPath, nextBlocks.length)
|
|
1393
|
-
);
|
|
1394
|
-
editor.setOption(AIChatPlugin, "_blockPath", lastBlock[1]);
|
|
1395
|
-
const lastBlockChunks = streamSerializeMd(
|
|
1396
|
-
editor,
|
|
1397
|
-
{
|
|
1398
|
-
value: [lastBlock[0]]
|
|
1399
|
-
},
|
|
1400
|
-
chunk
|
|
1401
|
-
);
|
|
1402
|
-
editor.setOption(AIChatPlugin, "_blockChunks", lastBlockChunks);
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
} else {
|
|
1406
|
-
const tempBlockChunks = _blockChunks + chunk;
|
|
1407
|
-
const tempBlocks = streamDeserializeMd(editor, tempBlockChunks);
|
|
1408
|
-
if (tempBlocks.length === 0) {
|
|
1409
|
-
return console.warn(
|
|
1410
|
-
`unsupport md nodes: ${JSON.stringify(tempBlockChunks)}`
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
if (tempBlocks.length === 1) {
|
|
1414
|
-
const currentBlock = editor.api.node(_blockPath)[0];
|
|
1415
|
-
if (isSameNode(editor, currentBlock, tempBlocks[0])) {
|
|
1416
|
-
const chunkNodes = streamDeserializeInlineMd(editor, chunk);
|
|
1417
|
-
editor.tf.insertNodes(nodesWithProps(editor, chunkNodes, options), {
|
|
1418
|
-
at: editor.api.end(_blockPath),
|
|
1419
|
-
select: true
|
|
1420
|
-
});
|
|
1421
|
-
const updatedBlock = editor.api.node(_blockPath);
|
|
1422
|
-
const serializedBlock = streamSerializeMd(
|
|
1423
|
-
editor,
|
|
1424
|
-
{
|
|
1425
|
-
value: [updatedBlock[0]]
|
|
1426
|
-
},
|
|
1427
|
-
tempBlockChunks
|
|
1428
|
-
);
|
|
1429
|
-
const blockText = NodeApi4.string(tempBlocks[0]);
|
|
1430
|
-
if (serializedBlock === tempBlockChunks && blockText === serializedBlock) {
|
|
1431
|
-
editor.setOption(AIChatPlugin, "_blockChunks", tempBlockChunks);
|
|
1432
|
-
} else {
|
|
1433
|
-
editor.tf.replaceNodes(
|
|
1434
|
-
nodesWithProps(editor, [tempBlocks[0]], options),
|
|
1435
|
-
{
|
|
1436
|
-
at: _blockPath,
|
|
1437
|
-
select: true
|
|
1438
|
-
}
|
|
1439
|
-
);
|
|
1440
|
-
const serializedBlock2 = streamSerializeMd(
|
|
1441
|
-
editor,
|
|
1442
|
-
{
|
|
1443
|
-
value: [tempBlocks[0]]
|
|
1444
|
-
},
|
|
1445
|
-
tempBlockChunks
|
|
1446
|
-
);
|
|
1447
|
-
editor.setOption(
|
|
1448
|
-
AIChatPlugin,
|
|
1449
|
-
"_blockChunks",
|
|
1450
|
-
// one block includes multiple children
|
|
1451
|
-
tempBlocks[0].type === getPluginType5(editor, KEYS17.codeBlock) || tempBlocks[0].type === getPluginType5(editor, KEYS17.table) || tempBlocks[0].type === getPluginType5(editor, KEYS17.equation) ? tempBlockChunks : serializedBlock2
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
} else {
|
|
1455
|
-
const serializedBlock = streamSerializeMd(
|
|
1456
|
-
editor,
|
|
1457
|
-
{
|
|
1458
|
-
value: [tempBlocks[0]]
|
|
1459
|
-
},
|
|
1460
|
-
tempBlockChunks
|
|
1461
|
-
);
|
|
1462
|
-
editor.tf.replaceNodes(
|
|
1463
|
-
nodesWithProps(editor, [tempBlocks[0]], options),
|
|
1464
|
-
{
|
|
1465
|
-
at: _blockPath,
|
|
1466
|
-
select: true
|
|
1467
|
-
}
|
|
1468
|
-
);
|
|
1469
|
-
editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
|
|
1470
|
-
}
|
|
1471
|
-
} else {
|
|
1472
|
-
editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
|
|
1473
|
-
at: _blockPath,
|
|
1474
|
-
select: true
|
|
1475
|
-
});
|
|
1476
|
-
if (tempBlocks.length > 1) {
|
|
1477
|
-
const newEndBlockPath = getNextPath(_blockPath, tempBlocks.length - 1);
|
|
1478
|
-
editor.tf.insertNodes(
|
|
1479
|
-
nodesWithProps(editor, tempBlocks.slice(1), options),
|
|
1480
|
-
{
|
|
1481
|
-
at: PathApi2.next(_blockPath),
|
|
1482
|
-
select: true
|
|
1483
|
-
}
|
|
1484
|
-
);
|
|
1485
|
-
editor.setOption(AIChatPlugin, "_blockPath", newEndBlockPath);
|
|
1486
|
-
const endBlock = editor.api.node(newEndBlockPath)[0];
|
|
1487
|
-
const serializedBlock = streamSerializeMd(
|
|
1488
|
-
editor,
|
|
1489
|
-
{
|
|
1490
|
-
value: [endBlock]
|
|
1491
|
-
},
|
|
1492
|
-
tempBlockChunks
|
|
1493
|
-
);
|
|
1494
|
-
editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
var getCurrentBlockPath = (editor) => {
|
|
1500
|
-
const getAnchorPreviousPath = (editor2) => {
|
|
1501
|
-
const anchorNode = editor2.getApi(AIChatPlugin).aiChat.node({ anchor: true });
|
|
1502
|
-
if (anchorNode) {
|
|
1503
|
-
return PathApi2.previous(anchorNode[1]);
|
|
1504
|
-
}
|
|
1505
|
-
};
|
|
1506
|
-
const getFocusPath = (editor2) => {
|
|
1507
|
-
return editor2.selection?.focus.path.slice(0, 1);
|
|
1508
|
-
};
|
|
1509
|
-
const path = getAnchorPreviousPath(editor) ?? getFocusPath(editor) ?? [0];
|
|
1510
|
-
const entry = editor.api.node(path);
|
|
1511
|
-
if (entry && (entry[0].type === getPluginType5(editor, KEYS17.columnGroup) || entry[0].type === getPluginType5(editor, KEYS17.table))) {
|
|
1512
|
-
return editor.api.above()?.[1] ?? path;
|
|
1513
|
-
}
|
|
1514
|
-
return path;
|
|
1515
|
-
};
|
|
1516
|
-
|
|
1517
|
-
// src/react/copilot/CopilotPlugin.tsx
|
|
1518
|
-
import { serializeMd } from "@platejs/markdown";
|
|
1519
|
-
import debounce from "lodash/debounce.js";
|
|
1520
|
-
import {
|
|
1521
|
-
bindFirst as bindFirst3,
|
|
1522
|
-
KEYS as KEYS22,
|
|
1523
|
-
NodeApi as NodeApi5
|
|
1524
|
-
} from "platejs";
|
|
1525
|
-
import { createTPlatePlugin as createTPlatePlugin2 } from "platejs/react";
|
|
1526
|
-
|
|
1527
|
-
// src/react/copilot/renderCopilotBelowNodes.tsx
|
|
1528
|
-
import React from "react";
|
|
1529
|
-
import { KEYS as KEYS18 } from "platejs";
|
|
1530
|
-
import { getEditorPlugin as getEditorPlugin4 } from "platejs/react";
|
|
1531
|
-
var renderCopilotBelowNodes = ({
|
|
1532
|
-
editor
|
|
1533
|
-
}) => {
|
|
1534
|
-
const copilot = getEditorPlugin4(editor, {
|
|
1535
|
-
key: KEYS18.copilot
|
|
1536
|
-
});
|
|
1537
|
-
const { renderGhostText: GhostText } = copilot.getOptions();
|
|
1538
|
-
if (!GhostText) return;
|
|
1539
|
-
return ({ children }) => {
|
|
1540
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement(GhostText, null));
|
|
1541
|
-
};
|
|
1542
|
-
};
|
|
1543
|
-
|
|
1544
|
-
// src/react/copilot/transforms/acceptCopilot.ts
|
|
1545
|
-
import { deserializeInlineMd } from "@platejs/markdown";
|
|
1546
|
-
import { KEYS as KEYS19 } from "platejs";
|
|
1547
|
-
var acceptCopilot = (editor) => {
|
|
1548
|
-
const { suggestionText } = editor.getOptions({
|
|
1549
|
-
key: KEYS19.copilot
|
|
1550
|
-
});
|
|
1551
|
-
if (!suggestionText?.length) return false;
|
|
1552
|
-
editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
|
|
1553
|
-
};
|
|
1554
|
-
|
|
1555
|
-
// src/react/copilot/transforms/acceptCopilotNextWord.ts
|
|
1556
|
-
import { deserializeInlineMd as deserializeInlineMd2 } from "@platejs/markdown";
|
|
1557
|
-
import { KEYS as KEYS21 } from "platejs";
|
|
1558
|
-
import { getEditorPlugin as getEditorPlugin6 } from "platejs/react";
|
|
1559
|
-
|
|
1560
|
-
// src/react/copilot/utils/callCompletionApi.ts
|
|
1561
|
-
var getOriginalFetch = () => fetch;
|
|
1562
|
-
async function callCompletionApi({
|
|
1563
|
-
api = "/api/completion",
|
|
1564
|
-
body,
|
|
1565
|
-
credentials,
|
|
1566
|
-
fetch: fetch2 = getOriginalFetch(),
|
|
1567
|
-
headers,
|
|
1568
|
-
prompt,
|
|
1569
|
-
setAbortController = () => {
|
|
1570
|
-
},
|
|
1571
|
-
setCompletion = () => {
|
|
1572
|
-
},
|
|
1573
|
-
setError = () => {
|
|
1574
|
-
},
|
|
1575
|
-
setLoading = () => {
|
|
1576
|
-
},
|
|
1577
|
-
onError,
|
|
1578
|
-
onFinish,
|
|
1579
|
-
onResponse
|
|
1580
|
-
}) {
|
|
1581
|
-
try {
|
|
1582
|
-
setLoading(true);
|
|
1583
|
-
setError(null);
|
|
1584
|
-
const abortController = new AbortController();
|
|
1585
|
-
setAbortController(abortController);
|
|
1586
|
-
setCompletion("");
|
|
1587
|
-
const res = await fetch2(api, {
|
|
1588
|
-
body: JSON.stringify({
|
|
1589
|
-
prompt,
|
|
1590
|
-
...body
|
|
1591
|
-
}),
|
|
1592
|
-
credentials,
|
|
1593
|
-
headers: {
|
|
1594
|
-
"Content-Type": "application/json",
|
|
1595
|
-
...headers
|
|
1596
|
-
},
|
|
1597
|
-
method: "POST",
|
|
1598
|
-
signal: abortController.signal
|
|
1599
|
-
}).catch((error) => {
|
|
1600
|
-
throw error;
|
|
1601
|
-
});
|
|
1602
|
-
if (onResponse) {
|
|
1603
|
-
await onResponse(res);
|
|
1604
|
-
}
|
|
1605
|
-
if (!res.ok) {
|
|
1606
|
-
throw new Error(
|
|
1607
|
-
await res.text() || "Failed to fetch the chat response."
|
|
1608
|
-
);
|
|
1609
|
-
}
|
|
1610
|
-
if (!res.body) {
|
|
1611
|
-
throw new Error("The response body is empty.");
|
|
1612
|
-
}
|
|
1613
|
-
const { text } = await res.json();
|
|
1614
|
-
if (!text) {
|
|
1615
|
-
throw new Error("The response does not contain a text field.");
|
|
1616
|
-
}
|
|
1617
|
-
setCompletion(text);
|
|
1618
|
-
if (onFinish) {
|
|
1619
|
-
onFinish(prompt, text);
|
|
1620
|
-
}
|
|
1621
|
-
setAbortController(null);
|
|
1622
|
-
return text;
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
if (error.name === "AbortError") {
|
|
1625
|
-
setAbortController(null);
|
|
1626
|
-
return null;
|
|
1627
|
-
}
|
|
1628
|
-
if (error instanceof Error && onError) {
|
|
1629
|
-
onError(error);
|
|
1630
|
-
}
|
|
1631
|
-
setError(error);
|
|
1632
|
-
} finally {
|
|
1633
|
-
setLoading(false);
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
// src/react/copilot/utils/getNextWord.ts
|
|
1638
|
-
var getNextWord = ({ text }) => {
|
|
1639
|
-
if (!text) return { firstWord: "", remainingText: "" };
|
|
1640
|
-
const nonSpaceMatch = /^\s*(\S)/.exec(text);
|
|
1641
|
-
if (!nonSpaceMatch) return { firstWord: "", remainingText: "" };
|
|
1642
|
-
const firstNonSpaceChar = nonSpaceMatch[1];
|
|
1643
|
-
const isCJKChar = /[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/.test(
|
|
1644
|
-
firstNonSpaceChar
|
|
1645
|
-
);
|
|
1646
|
-
let firstWord, remainingText;
|
|
1647
|
-
if (isCJKChar) {
|
|
1648
|
-
const match = /^(\s*)([\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF])([\u3000-\u303F\uFF00-\uFFEF])?/.exec(
|
|
1649
|
-
text
|
|
1650
|
-
);
|
|
1651
|
-
if (match) {
|
|
1652
|
-
const [fullMatch, spaces = "", char = "", punctuation = ""] = match;
|
|
1653
|
-
firstWord = spaces + char + punctuation;
|
|
1654
|
-
remainingText = text.slice(firstWord.length);
|
|
1655
|
-
} else {
|
|
1656
|
-
firstWord = "";
|
|
1657
|
-
remainingText = text;
|
|
1658
|
-
}
|
|
1659
|
-
} else {
|
|
1660
|
-
const match = /^(\s*\S+?)(?=[\s\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]|$)/.exec(
|
|
1661
|
-
text
|
|
1662
|
-
);
|
|
1663
|
-
if (match) {
|
|
1664
|
-
firstWord = match[0];
|
|
1665
|
-
remainingText = text.slice(firstWord.length);
|
|
1666
|
-
} else {
|
|
1667
|
-
firstWord = text;
|
|
1668
|
-
remainingText = "";
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
return { firstWord, remainingText };
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
// src/react/copilot/utils/triggerCopilotSuggestion.ts
|
|
1675
|
-
import { KEYS as KEYS20 } from "platejs";
|
|
1676
|
-
import { getEditorPlugin as getEditorPlugin5 } from "platejs/react";
|
|
1677
|
-
var triggerCopilotSuggestion = async (editor) => {
|
|
1678
|
-
const { api, getOptions, setOption } = getEditorPlugin5(
|
|
1679
|
-
editor,
|
|
1680
|
-
{
|
|
1681
|
-
key: KEYS20.copilot
|
|
1682
|
-
}
|
|
1683
|
-
);
|
|
1684
|
-
const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
|
|
1685
|
-
if (isLoading || editor.getOptions({ key: KEYS20.aiChat }).chat?.isLoading) {
|
|
1686
|
-
return false;
|
|
1687
|
-
}
|
|
1688
|
-
if (!triggerQuery({ editor })) return false;
|
|
1689
|
-
const prompt = getPrompt({ editor });
|
|
1690
|
-
if (prompt.length === 0) return false;
|
|
1691
|
-
api.copilot.stop();
|
|
1692
|
-
await callCompletionApi({
|
|
1693
|
-
prompt,
|
|
1694
|
-
onFinish: (_, completion) => {
|
|
1695
|
-
api.copilot.setBlockSuggestion({ text: completion });
|
|
1696
|
-
},
|
|
1697
|
-
...completeOptions,
|
|
1698
|
-
setAbortController: (controller) => setOption("abortController", controller),
|
|
1699
|
-
setCompletion: (completion) => setOption("completion", completion),
|
|
1700
|
-
setError: (error) => setOption("error", error),
|
|
1701
|
-
setLoading: (loading) => setOption("isLoading", loading),
|
|
1702
|
-
onError: (error) => {
|
|
1703
|
-
setOption("error", error);
|
|
1704
|
-
completeOptions?.onError?.(error);
|
|
1705
|
-
}
|
|
1706
|
-
});
|
|
1707
|
-
};
|
|
1708
|
-
|
|
1709
|
-
// src/react/copilot/utils/withoutAbort.ts
|
|
1710
|
-
var withoutAbort = (editor, fn) => {
|
|
1711
|
-
editor.setOption(CopilotPlugin, "shouldAbort", false);
|
|
1712
|
-
fn();
|
|
1713
|
-
editor.setOption(CopilotPlugin, "shouldAbort", true);
|
|
1714
|
-
};
|
|
1715
|
-
|
|
1716
|
-
// src/react/copilot/transforms/acceptCopilotNextWord.ts
|
|
1717
|
-
var acceptCopilotNextWord = (editor) => {
|
|
1718
|
-
const { api, getOptions } = getEditorPlugin6(editor, {
|
|
1719
|
-
key: KEYS21.copilot
|
|
1720
|
-
});
|
|
1721
|
-
const { getNextWord: getNextWord2, suggestionText } = getOptions();
|
|
1722
|
-
if (!suggestionText?.length) {
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
const { firstWord, remainingText } = getNextWord2({ text: suggestionText });
|
|
1726
|
-
api.copilot.setBlockSuggestion({
|
|
1727
|
-
text: remainingText
|
|
1728
|
-
});
|
|
1729
|
-
withoutAbort(editor, () => {
|
|
1730
|
-
editor.tf.insertFragment(deserializeInlineMd2(editor, firstWord));
|
|
1731
|
-
});
|
|
1732
|
-
};
|
|
1733
|
-
|
|
1734
|
-
// src/react/copilot/withCopilot.ts
|
|
1735
|
-
import { serializeInlineMd } from "@platejs/markdown";
|
|
1736
|
-
import {
|
|
1737
|
-
RangeApi as RangeApi2
|
|
1738
|
-
} from "platejs";
|
|
1739
|
-
var getPatchString = (editor, operations) => {
|
|
1740
|
-
let string = "";
|
|
1741
|
-
for (const operation of operations) {
|
|
1742
|
-
if (operation.type === "insert_node") {
|
|
1743
|
-
const node = operation.node;
|
|
1744
|
-
const text = serializeInlineMd(editor, { value: [node] });
|
|
1745
|
-
string += text;
|
|
1746
|
-
} else if (operation.type === "insert_text") {
|
|
1747
|
-
string += operation.text;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
return string;
|
|
1751
|
-
};
|
|
1752
|
-
var withCopilot = ({
|
|
1753
|
-
api,
|
|
1754
|
-
editor,
|
|
1755
|
-
getOptions,
|
|
1756
|
-
setOption,
|
|
1757
|
-
tf: { apply, insertText, redo, setSelection, undo, writeHistory }
|
|
1758
|
-
}) => {
|
|
1759
|
-
let prevSelection = null;
|
|
1760
|
-
return {
|
|
1761
|
-
transforms: {
|
|
1762
|
-
apply(operation) {
|
|
1763
|
-
const { shouldAbort } = getOptions();
|
|
1764
|
-
if (shouldAbort) {
|
|
1765
|
-
api.copilot.reject();
|
|
1766
|
-
}
|
|
1767
|
-
apply(operation);
|
|
1768
|
-
},
|
|
1769
|
-
insertText(text, options) {
|
|
1770
|
-
const suggestionText = getOptions().suggestionText;
|
|
1771
|
-
if (suggestionText?.startsWith(text)) {
|
|
1772
|
-
withoutAbort(editor, () => {
|
|
1773
|
-
editor.tf.withoutMerging(() => {
|
|
1774
|
-
const newText = suggestionText?.slice(text.length);
|
|
1775
|
-
setOption("suggestionText", newText);
|
|
1776
|
-
insertText(text);
|
|
1777
|
-
});
|
|
1778
|
-
});
|
|
1779
|
-
return;
|
|
1780
|
-
}
|
|
1781
|
-
insertText(text, options);
|
|
1782
|
-
},
|
|
1783
|
-
redo() {
|
|
1784
|
-
if (!getOptions().suggestionText) return redo();
|
|
1785
|
-
const topRedo = editor.history.redos.at(-1);
|
|
1786
|
-
const prevSuggestion = getOptions().suggestionText;
|
|
1787
|
-
if (topRedo && topRedo.shouldAbort === false && prevSuggestion) {
|
|
1788
|
-
withoutAbort(editor, () => {
|
|
1789
|
-
const shouldRemoveText = getPatchString(editor, topRedo.operations);
|
|
1790
|
-
const newText = prevSuggestion.slice(shouldRemoveText.length);
|
|
1791
|
-
setOption("suggestionText", newText);
|
|
1792
|
-
redo();
|
|
1793
|
-
});
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
return redo();
|
|
1797
|
-
},
|
|
1798
|
-
setSelection(props) {
|
|
1799
|
-
setSelection(props);
|
|
1800
|
-
if (editor.selection && (!prevSelection || !RangeApi2.equals(prevSelection, editor.selection)) && getOptions().autoTriggerQuery({ editor }) && editor.api.isFocused()) {
|
|
1801
|
-
void api.copilot.triggerSuggestion();
|
|
1802
|
-
}
|
|
1803
|
-
prevSelection = editor.selection;
|
|
1804
|
-
},
|
|
1805
|
-
undo() {
|
|
1806
|
-
if (!getOptions().suggestionText) return undo();
|
|
1807
|
-
const lastUndos = editor.history.undos.at(-1);
|
|
1808
|
-
const oldText = getOptions().suggestionText;
|
|
1809
|
-
if (lastUndos && lastUndos.shouldAbort === false && oldText) {
|
|
1810
|
-
withoutAbort(editor, () => {
|
|
1811
|
-
const shouldInsertText = getPatchString(
|
|
1812
|
-
editor,
|
|
1813
|
-
lastUndos.operations
|
|
1814
|
-
);
|
|
1815
|
-
const newText = shouldInsertText + oldText;
|
|
1816
|
-
setOption("suggestionText", newText);
|
|
1817
|
-
undo();
|
|
1818
|
-
});
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
return undo();
|
|
1822
|
-
},
|
|
1823
|
-
writeHistory(stacks, batch) {
|
|
1824
|
-
if (!getOptions().isLoading) {
|
|
1825
|
-
batch.shouldAbort = getOptions().shouldAbort;
|
|
1826
|
-
}
|
|
1827
|
-
return writeHistory(stacks, batch);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
};
|
|
1831
|
-
};
|
|
1832
|
-
|
|
1833
|
-
// src/react/copilot/CopilotPlugin.tsx
|
|
1834
|
-
var CopilotPlugin = createTPlatePlugin2({
|
|
1835
|
-
key: KEYS22.copilot,
|
|
1836
|
-
handlers: {
|
|
1837
|
-
onBlur: ({ api }) => {
|
|
1838
|
-
api.copilot.reject();
|
|
1839
|
-
},
|
|
1840
|
-
onMouseDown: ({ api }) => {
|
|
1841
|
-
api.copilot.reject();
|
|
1842
|
-
}
|
|
1843
|
-
},
|
|
1844
|
-
options: {
|
|
1845
|
-
abortController: null,
|
|
1846
|
-
completeOptions: {},
|
|
1847
|
-
completion: "",
|
|
1848
|
-
debounceDelay: 0,
|
|
1849
|
-
error: null,
|
|
1850
|
-
getNextWord,
|
|
1851
|
-
isLoading: false,
|
|
1852
|
-
renderGhostText: null,
|
|
1853
|
-
shouldAbort: true,
|
|
1854
|
-
suggestionNodeId: null,
|
|
1855
|
-
suggestionText: null,
|
|
1856
|
-
autoTriggerQuery: ({ editor }) => {
|
|
1857
|
-
if (editor.getOptions({ key: KEYS22.copilot }).suggestionText) {
|
|
1858
|
-
return false;
|
|
1859
|
-
}
|
|
1860
|
-
const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
|
|
1861
|
-
if (isEmpty) return false;
|
|
1862
|
-
const blockAbove = editor.api.block();
|
|
1863
|
-
if (!blockAbove) return false;
|
|
1864
|
-
const blockString = NodeApi5.string(blockAbove[0]);
|
|
1865
|
-
return blockString.at(-1) === " ";
|
|
1866
|
-
},
|
|
1867
|
-
getPrompt: ({ editor }) => {
|
|
1868
|
-
const contextEntry = editor.api.block({ highest: true });
|
|
1869
|
-
if (!contextEntry) return "";
|
|
1870
|
-
return serializeMd(editor, {
|
|
1871
|
-
value: [contextEntry[0]]
|
|
1872
|
-
});
|
|
1873
|
-
},
|
|
1874
|
-
triggerQuery: ({ editor }) => {
|
|
1875
|
-
if (editor.api.isExpanded()) return false;
|
|
1876
|
-
const isEnd = editor.api.isAt({ end: true });
|
|
1877
|
-
if (!isEnd) return false;
|
|
1878
|
-
return true;
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}).overrideEditor(withCopilot).extendSelectors(({ getOptions }) => ({
|
|
1882
|
-
isSuggested: (id) => getOptions().suggestionNodeId === id
|
|
1883
|
-
})).extendTransforms(({ editor }) => ({
|
|
1884
|
-
accept: bindFirst3(acceptCopilot, editor),
|
|
1885
|
-
acceptNextWord: bindFirst3(acceptCopilotNextWord, editor)
|
|
1886
|
-
})).extendApi(
|
|
1887
|
-
({ api, editor, getOptions, setOption, setOptions }) => {
|
|
1888
|
-
const debounceDelay = getOptions().debounceDelay;
|
|
1889
|
-
let triggerSuggestion = bindFirst3(triggerCopilotSuggestion, editor);
|
|
1890
|
-
if (debounceDelay) {
|
|
1891
|
-
triggerSuggestion = debounce(
|
|
1892
|
-
bindFirst3(triggerCopilotSuggestion, editor),
|
|
1893
|
-
debounceDelay
|
|
1894
|
-
);
|
|
1895
|
-
}
|
|
1896
|
-
return {
|
|
1897
|
-
triggerSuggestion,
|
|
1898
|
-
setBlockSuggestion: ({ id = getOptions().suggestionNodeId, text }) => {
|
|
1899
|
-
if (!id) {
|
|
1900
|
-
id = editor.api.block()[0].id;
|
|
1901
|
-
}
|
|
1902
|
-
setOptions({
|
|
1903
|
-
suggestionNodeId: id,
|
|
1904
|
-
suggestionText: text
|
|
1905
|
-
});
|
|
1906
|
-
},
|
|
1907
|
-
stop: () => {
|
|
1908
|
-
const { abortController } = getOptions();
|
|
1909
|
-
api.copilot.triggerSuggestion?.cancel();
|
|
1910
|
-
if (abortController) {
|
|
1911
|
-
abortController.abort();
|
|
1912
|
-
setOption("abortController", null);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
).extendApi(({ api, getOptions, setOptions }) => ({
|
|
1918
|
-
reject: () => {
|
|
1919
|
-
if (!getOptions().suggestionText?.length) return false;
|
|
1920
|
-
api.copilot.stop();
|
|
1921
|
-
setOptions({
|
|
1922
|
-
completion: null,
|
|
1923
|
-
suggestionNodeId: null,
|
|
1924
|
-
suggestionText: null
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
})).extend({
|
|
1928
|
-
render: {
|
|
1929
|
-
belowNodes: renderCopilotBelowNodes
|
|
1930
|
-
},
|
|
1931
|
-
shortcuts: {
|
|
1932
|
-
accept: {
|
|
1933
|
-
keys: "tab"
|
|
1934
|
-
},
|
|
1935
|
-
reject: {
|
|
1936
|
-
keys: "escape"
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
});
|
|
1940
|
-
export {
|
|
1941
|
-
AIChatPlugin,
|
|
1942
|
-
AIPlugin,
|
|
1943
|
-
CopilotPlugin,
|
|
1944
|
-
acceptAIChat,
|
|
1945
|
-
acceptAISuggestions,
|
|
1946
|
-
acceptCopilot,
|
|
1947
|
-
acceptCopilotNextWord,
|
|
1948
|
-
aiCommentToRange,
|
|
1949
|
-
applyAISuggestions,
|
|
1950
|
-
callCompletionApi,
|
|
1951
|
-
createFormattedBlocks,
|
|
1952
|
-
escapeInput,
|
|
1953
|
-
findTextRangeInBlock,
|
|
1954
|
-
getChunkTrimmed,
|
|
1955
|
-
getCurrentBlockPath,
|
|
1956
|
-
getLastAssistantMessage,
|
|
1957
|
-
getListNode,
|
|
1958
|
-
getNextWord,
|
|
1959
|
-
insertBelowAIChat,
|
|
1960
|
-
insertBelowGenerate,
|
|
1961
|
-
isCompleteCodeBlock,
|
|
1962
|
-
isCompleteMath,
|
|
1963
|
-
isSameNode,
|
|
1964
|
-
nodesWithProps,
|
|
1965
|
-
rejectAISuggestions,
|
|
1966
|
-
removeAnchorAIChat,
|
|
1967
|
-
renderCopilotBelowNodes,
|
|
1968
|
-
replaceSelectionAIChat,
|
|
1969
|
-
resetAIChat,
|
|
1970
|
-
streamDeserializeInlineMd,
|
|
1971
|
-
streamDeserializeMd,
|
|
1972
|
-
streamInsertChunk,
|
|
1973
|
-
streamSerializeMd,
|
|
1974
|
-
submitAIChat,
|
|
1975
|
-
triggerCopilotSuggestion,
|
|
1976
|
-
useAIChatEditor,
|
|
1977
|
-
useChatChunk,
|
|
1978
|
-
useEditorChat,
|
|
1979
|
-
useLastAssistantMessage,
|
|
1980
|
-
withAIChat,
|
|
1981
|
-
withCopilot,
|
|
1982
|
-
withoutAbort
|
|
1983
|
-
};
|
|
1984
|
-
//# sourceMappingURL=index.mjs.map
|