@platejs/ai 49.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/README.md +7 -0
- package/dist/BaseAIPlugin-DyivsvyQ.d.mts +36 -0
- package/dist/BaseAIPlugin-DyivsvyQ.d.ts +36 -0
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +992 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +953 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +435 -0
- package/dist/react/index.d.ts +435 -0
- package/dist/react/index.js +1215 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1174 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/react/index.ts
|
|
31
|
+
var react_exports = {};
|
|
32
|
+
__export(react_exports, {
|
|
33
|
+
AIChatPlugin: () => AIChatPlugin,
|
|
34
|
+
AIPlugin: () => AIPlugin,
|
|
35
|
+
CopilotPlugin: () => CopilotPlugin,
|
|
36
|
+
acceptAIChat: () => acceptAIChat,
|
|
37
|
+
acceptCopilot: () => acceptCopilot,
|
|
38
|
+
acceptCopilotNextWord: () => acceptCopilotNextWord,
|
|
39
|
+
callCompletionApi: () => callCompletionApi,
|
|
40
|
+
createFormattedBlocks: () => createFormattedBlocks,
|
|
41
|
+
getEditorPrompt: () => getEditorPrompt,
|
|
42
|
+
getLastAssistantMessage: () => getLastAssistantMessage,
|
|
43
|
+
getMarkdown: () => getMarkdown,
|
|
44
|
+
getNextWord: () => getNextWord,
|
|
45
|
+
insertBelowAIChat: () => insertBelowAIChat,
|
|
46
|
+
removeAnchorAIChat: () => removeAnchorAIChat,
|
|
47
|
+
renderCopilotBelowNodes: () => renderCopilotBelowNodes,
|
|
48
|
+
replaceSelectionAIChat: () => replaceSelectionAIChat,
|
|
49
|
+
resetAIChat: () => resetAIChat,
|
|
50
|
+
submitAIChat: () => submitAIChat,
|
|
51
|
+
triggerCopilotSuggestion: () => triggerCopilotSuggestion,
|
|
52
|
+
useAIChatEditor: () => useAIChatEditor,
|
|
53
|
+
useChatChunk: () => useChatChunk,
|
|
54
|
+
useEditorChat: () => useEditorChat,
|
|
55
|
+
useLastAssistantMessage: () => useLastAssistantMessage,
|
|
56
|
+
withAIChat: () => withAIChat,
|
|
57
|
+
withCopilot: () => withCopilot,
|
|
58
|
+
withoutAbort: () => withoutAbort
|
|
59
|
+
});
|
|
60
|
+
module.exports = __toCommonJS(react_exports);
|
|
61
|
+
|
|
62
|
+
// src/react/ai/AIPlugin.ts
|
|
63
|
+
var import_react = require("platejs/react");
|
|
64
|
+
|
|
65
|
+
// src/lib/BaseAIPlugin.ts
|
|
66
|
+
var import_platejs2 = require("platejs");
|
|
67
|
+
|
|
68
|
+
// src/lib/transforms/insertAINodes.ts
|
|
69
|
+
var insertAINodes = (editor, nodes, {
|
|
70
|
+
target
|
|
71
|
+
} = {}) => {
|
|
72
|
+
if (!target && !editor.selection?.focus.path) return;
|
|
73
|
+
const aiNodes = nodes.map((node) => ({
|
|
74
|
+
...node,
|
|
75
|
+
ai: true
|
|
76
|
+
}));
|
|
77
|
+
editor.tf.withoutNormalizing(() => {
|
|
78
|
+
editor.tf.insertNodes(aiNodes, {
|
|
79
|
+
at: editor.api.end(target || editor.selection.focus.path),
|
|
80
|
+
select: true
|
|
81
|
+
});
|
|
82
|
+
editor.tf.collapse({ edge: "end" });
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/lib/transforms/removeAIMarks.ts
|
|
87
|
+
var removeAIMarks = (editor, { at = [] } = {}) => {
|
|
88
|
+
editor.tf.unsetNodes("ai", {
|
|
89
|
+
at,
|
|
90
|
+
match: (n) => n.ai
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/lib/transforms/removeAINodes.ts
|
|
95
|
+
var import_platejs = require("platejs");
|
|
96
|
+
var removeAINodes = (editor, { at = [] } = {}) => {
|
|
97
|
+
editor.tf.removeNodes({
|
|
98
|
+
at,
|
|
99
|
+
match: (n) => import_platejs.TextApi.isText(n) && !!n.ai
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/lib/transforms/undoAI.ts
|
|
104
|
+
var undoAI = (editor) => {
|
|
105
|
+
if (editor.history.undos.at(-1)?.ai && editor.api.some({
|
|
106
|
+
at: [],
|
|
107
|
+
match: (n) => !!n.ai
|
|
108
|
+
})) {
|
|
109
|
+
editor.undo();
|
|
110
|
+
editor.history.redos.pop();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/lib/transforms/withAIBatch.ts
|
|
115
|
+
var withAIBatch = (editor, fn, {
|
|
116
|
+
split
|
|
117
|
+
} = {}) => {
|
|
118
|
+
if (split) {
|
|
119
|
+
editor.tf.withNewBatch(fn);
|
|
120
|
+
} else {
|
|
121
|
+
editor.tf.withMerging(fn);
|
|
122
|
+
}
|
|
123
|
+
const lastBatch = editor.history.undos?.at(-1);
|
|
124
|
+
if (lastBatch) {
|
|
125
|
+
lastBatch.ai = true;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/lib/BaseAIPlugin.ts
|
|
130
|
+
var BaseAIPlugin = (0, import_platejs2.createTSlatePlugin)({
|
|
131
|
+
key: import_platejs2.KEYS.ai,
|
|
132
|
+
node: { isDecoration: false, isLeaf: true }
|
|
133
|
+
}).extendTransforms(({ editor }) => ({
|
|
134
|
+
insertNodes: (0, import_platejs2.bindFirst)(insertAINodes, editor),
|
|
135
|
+
removeMarks: (0, import_platejs2.bindFirst)(removeAIMarks, editor),
|
|
136
|
+
removeNodes: (0, import_platejs2.bindFirst)(removeAINodes, editor),
|
|
137
|
+
undo: (0, import_platejs2.bindFirst)(undoAI, editor)
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
// src/react/ai/AIPlugin.ts
|
|
141
|
+
var AIPlugin = (0, import_react.toPlatePlugin)(BaseAIPlugin);
|
|
142
|
+
|
|
143
|
+
// src/react/ai-chat/AIChatPlugin.ts
|
|
144
|
+
var import_react9 = require("@platejs/selection/react");
|
|
145
|
+
var import_platejs11 = require("platejs");
|
|
146
|
+
var import_react10 = require("platejs/react");
|
|
147
|
+
|
|
148
|
+
// src/react/ai-chat/transforms/acceptAIChat.ts
|
|
149
|
+
var import_platejs3 = require("platejs");
|
|
150
|
+
var import_react2 = require("platejs/react");
|
|
151
|
+
var acceptAIChat = (editor) => {
|
|
152
|
+
const { tf } = (0, import_react2.getEditorPlugin)(editor, AIPlugin);
|
|
153
|
+
const api = editor.getApi({ key: import_platejs3.KEYS.ai });
|
|
154
|
+
const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
|
|
155
|
+
withAIBatch(editor, () => {
|
|
156
|
+
tf.ai.removeMarks();
|
|
157
|
+
editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
|
|
158
|
+
});
|
|
159
|
+
api.aiChat.hide();
|
|
160
|
+
editor.tf.focus();
|
|
161
|
+
const focusPoint = editor.api.end(lastAINodePath);
|
|
162
|
+
editor.tf.setSelection({
|
|
163
|
+
anchor: focusPoint,
|
|
164
|
+
focus: focusPoint
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/react/ai-chat/transforms/insertBelowAIChat.ts
|
|
169
|
+
var import_react4 = require("@platejs/selection/react");
|
|
170
|
+
var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"));
|
|
171
|
+
var import_platejs5 = require("platejs");
|
|
172
|
+
|
|
173
|
+
// src/react/ai-chat/transforms/replaceSelectionAIChat.ts
|
|
174
|
+
var import_react3 = require("@platejs/selection/react");
|
|
175
|
+
var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"));
|
|
176
|
+
var import_platejs4 = require("platejs");
|
|
177
|
+
var createFormattedBlocks = ({
|
|
178
|
+
blocks,
|
|
179
|
+
format,
|
|
180
|
+
sourceBlock
|
|
181
|
+
}) => {
|
|
182
|
+
if (format === "none") return (0, import_cloneDeep.default)(blocks);
|
|
183
|
+
const [sourceNode] = sourceBlock;
|
|
184
|
+
const firstTextEntry = import_platejs4.NodeApi.firstText(sourceNode);
|
|
185
|
+
if (!firstTextEntry) return null;
|
|
186
|
+
const blockProps = import_platejs4.NodeApi.extractProps(sourceNode);
|
|
187
|
+
const textProps = import_platejs4.NodeApi.extractProps(firstTextEntry[0]);
|
|
188
|
+
const applyTextFormatting = (node) => {
|
|
189
|
+
if (import_platejs4.TextApi.isText(node)) {
|
|
190
|
+
return { ...textProps, ...node };
|
|
191
|
+
}
|
|
192
|
+
if (node.children) {
|
|
193
|
+
return {
|
|
194
|
+
...node,
|
|
195
|
+
children: node.children.map(applyTextFormatting)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return node;
|
|
199
|
+
};
|
|
200
|
+
return blocks.map((block, index) => {
|
|
201
|
+
if (format === "single" && index > 0) {
|
|
202
|
+
return block;
|
|
203
|
+
}
|
|
204
|
+
return applyTextFormatting({
|
|
205
|
+
...block,
|
|
206
|
+
...blockProps
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
var replaceSelectionAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
|
|
211
|
+
if (!sourceEditor || sourceEditor.api.isEmpty()) return;
|
|
212
|
+
const isBlockSelecting = editor.getOption(
|
|
213
|
+
import_react3.BlockSelectionPlugin,
|
|
214
|
+
"isSelectingSome"
|
|
215
|
+
);
|
|
216
|
+
editor.getApi({ key: import_platejs4.KEYS.ai }).aiChat.hide();
|
|
217
|
+
if (!isBlockSelecting) {
|
|
218
|
+
const firstBlock = editor.api.node({
|
|
219
|
+
block: true,
|
|
220
|
+
mode: "lowest"
|
|
221
|
+
});
|
|
222
|
+
if (firstBlock && editor.api.isSelected(firstBlock[1], { contains: true }) && format !== "none") {
|
|
223
|
+
const formattedBlocks2 = createFormattedBlocks({
|
|
224
|
+
blocks: (0, import_cloneDeep.default)(sourceEditor.children),
|
|
225
|
+
format,
|
|
226
|
+
sourceBlock: firstBlock
|
|
227
|
+
});
|
|
228
|
+
if (!formattedBlocks2) return;
|
|
229
|
+
editor.tf.insertFragment(formattedBlocks2);
|
|
230
|
+
editor.tf.focus();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
editor.tf.insertFragment(sourceEditor.children);
|
|
234
|
+
editor.tf.focus();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const blockSelectionApi = editor.getApi(import_react3.BlockSelectionPlugin).blockSelection;
|
|
238
|
+
const selectedBlocks = blockSelectionApi.getNodes();
|
|
239
|
+
if (selectedBlocks.length === 0) return;
|
|
240
|
+
if (format === "none" || format === "single" && selectedBlocks.length > 1) {
|
|
241
|
+
editor.tf.withoutNormalizing(() => {
|
|
242
|
+
(0, import_react3.removeBlockSelectionNodes)(editor);
|
|
243
|
+
editor.tf.withNewBatch(() => {
|
|
244
|
+
editor.getTransforms(import_react3.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(
|
|
245
|
+
(0, import_cloneDeep.default)(sourceEditor.children),
|
|
246
|
+
{
|
|
247
|
+
at: selectedBlocks[0][1]
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
editor.getApi(import_react3.BlockSelectionPlugin).blockSelection.focus();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const [, firstBlockPath] = selectedBlocks[0];
|
|
256
|
+
const formattedBlocks = createFormattedBlocks({
|
|
257
|
+
blocks: (0, import_cloneDeep.default)(sourceEditor.children),
|
|
258
|
+
format,
|
|
259
|
+
sourceBlock: selectedBlocks[0]
|
|
260
|
+
});
|
|
261
|
+
if (!formattedBlocks) return;
|
|
262
|
+
editor.tf.withoutNormalizing(() => {
|
|
263
|
+
(0, import_react3.removeBlockSelectionNodes)(editor);
|
|
264
|
+
editor.tf.withNewBatch(() => {
|
|
265
|
+
editor.getTransforms(import_react3.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(formattedBlocks, {
|
|
266
|
+
at: firstBlockPath
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
editor.getApi(import_react3.BlockSelectionPlugin).blockSelection.focus();
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/react/ai-chat/transforms/insertBelowAIChat.ts
|
|
274
|
+
var insertBelowAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
|
|
275
|
+
if (!sourceEditor || sourceEditor.api.isEmpty()) return;
|
|
276
|
+
const isBlockSelecting = editor.getOption(
|
|
277
|
+
import_react4.BlockSelectionPlugin,
|
|
278
|
+
"isSelectingSome"
|
|
279
|
+
);
|
|
280
|
+
editor.getApi({ key: import_platejs5.KEYS.ai }).aiChat.hide();
|
|
281
|
+
const insertBlocksAndSelect = editor.getTransforms(import_react4.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect;
|
|
282
|
+
if (isBlockSelecting) {
|
|
283
|
+
const selectedBlocks = editor.getApi(import_react4.BlockSelectionPlugin).blockSelection.getNodes();
|
|
284
|
+
const selectedIds = editor.getOptions(import_react4.BlockSelectionPlugin).selectedIds;
|
|
285
|
+
if (!selectedIds || selectedIds.size === 0) return;
|
|
286
|
+
const lastBlock = selectedBlocks.at(-1);
|
|
287
|
+
if (!lastBlock) return;
|
|
288
|
+
const nextPath = import_platejs5.PathApi.next(lastBlock[1]);
|
|
289
|
+
if (format === "none") {
|
|
290
|
+
insertBlocksAndSelect((0, import_cloneDeep2.default)(sourceEditor.children), {
|
|
291
|
+
at: nextPath
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const formattedBlocks = createFormattedBlocks({
|
|
296
|
+
blocks: (0, import_cloneDeep2.default)(sourceEditor.children),
|
|
297
|
+
format,
|
|
298
|
+
sourceBlock: lastBlock
|
|
299
|
+
});
|
|
300
|
+
if (!formattedBlocks) return;
|
|
301
|
+
insertBlocksAndSelect(formattedBlocks, {
|
|
302
|
+
at: nextPath
|
|
303
|
+
});
|
|
304
|
+
} else {
|
|
305
|
+
const [, end] = import_platejs5.RangeApi.edges(editor.selection);
|
|
306
|
+
const endPath = [end.path[0]];
|
|
307
|
+
const currentBlock = editor.api.node({
|
|
308
|
+
at: endPath,
|
|
309
|
+
block: true,
|
|
310
|
+
mode: "lowest"
|
|
311
|
+
});
|
|
312
|
+
if (!currentBlock) return;
|
|
313
|
+
if (format === "none") {
|
|
314
|
+
insertBlocksAndSelect((0, import_cloneDeep2.default)(sourceEditor.children), {
|
|
315
|
+
at: import_platejs5.PathApi.next(endPath)
|
|
316
|
+
});
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const formattedBlocks = createFormattedBlocks({
|
|
320
|
+
blocks: (0, import_cloneDeep2.default)(sourceEditor.children),
|
|
321
|
+
format,
|
|
322
|
+
sourceBlock: currentBlock
|
|
323
|
+
});
|
|
324
|
+
if (!formattedBlocks) return;
|
|
325
|
+
insertBlocksAndSelect(formattedBlocks, {
|
|
326
|
+
at: import_platejs5.PathApi.next(endPath)
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/react/ai-chat/transforms/removeAnchorAIChat.ts
|
|
332
|
+
var import_platejs6 = require("platejs");
|
|
333
|
+
var removeAnchorAIChat = (editor, options) => {
|
|
334
|
+
editor.tf.withoutSaving(() => {
|
|
335
|
+
editor.tf.removeNodes({
|
|
336
|
+
at: [],
|
|
337
|
+
match: (n) => import_platejs6.ElementApi.isElement(n) && n.type === import_platejs6.KEYS.aiChat,
|
|
338
|
+
...options
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/react/ai-chat/utils/getEditorPrompt.ts
|
|
344
|
+
var import_selection = require("@platejs/selection");
|
|
345
|
+
var import_react6 = require("@platejs/selection/react");
|
|
346
|
+
|
|
347
|
+
// src/react/ai-chat/utils/getMarkdown.ts
|
|
348
|
+
var import_markdown = require("@platejs/markdown");
|
|
349
|
+
var import_react5 = require("@platejs/selection/react");
|
|
350
|
+
var import_platejs7 = require("platejs");
|
|
351
|
+
var getMarkdown = (editor, type) => {
|
|
352
|
+
if (type === "editor") {
|
|
353
|
+
return (0, import_markdown.serializeMd)(editor);
|
|
354
|
+
}
|
|
355
|
+
if (type === "block") {
|
|
356
|
+
const blocks = editor.getOption(import_react5.BlockSelectionPlugin, "isSelectingSome") ? editor.getApi(import_react5.BlockSelectionPlugin).blockSelection.getNodes() : editor.api.nodes({
|
|
357
|
+
mode: "highest",
|
|
358
|
+
match: (n) => editor.api.isBlock(n)
|
|
359
|
+
});
|
|
360
|
+
const nodes = Array.from(blocks, (entry) => entry[0]);
|
|
361
|
+
return (0, import_markdown.serializeMd)(editor, { value: nodes });
|
|
362
|
+
}
|
|
363
|
+
if (type === "selection") {
|
|
364
|
+
const fragment = editor.api.fragment();
|
|
365
|
+
if (fragment.length === 1) {
|
|
366
|
+
const modifiedFragment = [
|
|
367
|
+
{
|
|
368
|
+
children: fragment[0].children,
|
|
369
|
+
type: import_platejs7.KEYS.p
|
|
370
|
+
}
|
|
371
|
+
];
|
|
372
|
+
return (0, import_markdown.serializeMd)(editor, { value: modifiedFragment });
|
|
373
|
+
}
|
|
374
|
+
return (0, import_markdown.serializeMd)(editor, { value: fragment });
|
|
375
|
+
}
|
|
376
|
+
return "";
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/react/ai-chat/utils/getEditorPrompt.ts
|
|
380
|
+
var replacePlaceholders = (editor, text, {
|
|
381
|
+
prompt
|
|
382
|
+
}) => {
|
|
383
|
+
let result = text.replace("{prompt}", prompt || "");
|
|
384
|
+
const placeholders = {
|
|
385
|
+
"{block}": "block",
|
|
386
|
+
"{editor}": "editor",
|
|
387
|
+
"{selection}": "selection"
|
|
388
|
+
};
|
|
389
|
+
Object.entries(placeholders).forEach(([placeholder, type]) => {
|
|
390
|
+
if (result.includes(placeholder)) {
|
|
391
|
+
result = result.replace(placeholder, getMarkdown(editor, type));
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
return result;
|
|
395
|
+
};
|
|
396
|
+
var createPromptFromConfig = (config, params) => {
|
|
397
|
+
const { isBlockSelecting, isSelecting: isSelecting3 } = params;
|
|
398
|
+
if (isBlockSelecting && config.blockSelecting) {
|
|
399
|
+
return config.blockSelecting ?? config.default;
|
|
400
|
+
} else if (isSelecting3 && config.selecting) {
|
|
401
|
+
return config.selecting ?? config.default;
|
|
402
|
+
} else {
|
|
403
|
+
return config.default;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
var getEditorPrompt = (editor, {
|
|
407
|
+
prompt = "",
|
|
408
|
+
promptTemplate = () => "{prompt}"
|
|
409
|
+
} = {}) => {
|
|
410
|
+
const params = {
|
|
411
|
+
editor,
|
|
412
|
+
isBlockSelecting: editor.getOption(import_react6.BlockSelectionPlugin, "isSelectingSome"),
|
|
413
|
+
isSelecting: (0, import_selection.isSelecting)(editor)
|
|
414
|
+
};
|
|
415
|
+
const template = promptTemplate(params);
|
|
416
|
+
if (!template) return;
|
|
417
|
+
let promptText = "";
|
|
418
|
+
if (typeof prompt === "function") {
|
|
419
|
+
promptText = prompt(params);
|
|
420
|
+
} else if (typeof prompt === "object") {
|
|
421
|
+
promptText = createPromptFromConfig(prompt, params);
|
|
422
|
+
} else {
|
|
423
|
+
promptText = prompt;
|
|
424
|
+
}
|
|
425
|
+
return replacePlaceholders(editor, template, {
|
|
426
|
+
prompt: promptText
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/react/ai-chat/utils/resetAIChat.ts
|
|
431
|
+
var import_platejs8 = require("platejs");
|
|
432
|
+
var import_react7 = require("platejs/react");
|
|
433
|
+
var resetAIChat = (editor) => {
|
|
434
|
+
const { api, getOptions } = (0, import_react7.getEditorPlugin)(editor, {
|
|
435
|
+
key: import_platejs8.KEYS.aiChat
|
|
436
|
+
});
|
|
437
|
+
api.aiChat.stop();
|
|
438
|
+
const chat = getOptions().chat;
|
|
439
|
+
if (chat.messages && chat.messages.length > 0) {
|
|
440
|
+
chat.setMessages?.([]);
|
|
441
|
+
}
|
|
442
|
+
editor.getTransforms(AIPlugin).ai.undo();
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// src/react/ai-chat/utils/submitAIChat.ts
|
|
446
|
+
var import_selection2 = require("@platejs/selection");
|
|
447
|
+
var import_platejs9 = require("platejs");
|
|
448
|
+
var import_react8 = require("platejs/react");
|
|
449
|
+
var submitAIChat = (editor, {
|
|
450
|
+
mode,
|
|
451
|
+
options,
|
|
452
|
+
prompt,
|
|
453
|
+
system
|
|
454
|
+
} = {}) => {
|
|
455
|
+
const { getOptions, setOption } = (0, import_react8.getEditorPlugin)(
|
|
456
|
+
editor,
|
|
457
|
+
{
|
|
458
|
+
key: import_platejs9.KEYS.aiChat
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
const { chat, promptTemplate, systemTemplate } = getOptions();
|
|
462
|
+
if (!prompt && chat.input?.length === 0) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!prompt) {
|
|
466
|
+
prompt = chat.input;
|
|
467
|
+
}
|
|
468
|
+
if (!mode) {
|
|
469
|
+
mode = (0, import_selection2.isSelecting)(editor) ? "chat" : "insert";
|
|
470
|
+
}
|
|
471
|
+
if (mode === "insert") {
|
|
472
|
+
editor.getTransforms(AIPlugin).ai.undo();
|
|
473
|
+
}
|
|
474
|
+
setOption("mode", mode);
|
|
475
|
+
chat.setInput?.("");
|
|
476
|
+
void chat.append?.(
|
|
477
|
+
{
|
|
478
|
+
content: getEditorPrompt(editor, {
|
|
479
|
+
prompt,
|
|
480
|
+
promptTemplate
|
|
481
|
+
}) ?? "",
|
|
482
|
+
role: "user"
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
body: {
|
|
486
|
+
system: getEditorPrompt(editor, {
|
|
487
|
+
prompt: system,
|
|
488
|
+
promptTemplate: systemTemplate
|
|
489
|
+
})
|
|
490
|
+
},
|
|
491
|
+
...options
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// src/react/ai-chat/withAIChat.ts
|
|
497
|
+
var import_platejs10 = require("platejs");
|
|
498
|
+
var withAIChat = ({
|
|
499
|
+
api,
|
|
500
|
+
editor,
|
|
501
|
+
getOptions,
|
|
502
|
+
tf: { insertText, normalizeNode, setSelection },
|
|
503
|
+
type
|
|
504
|
+
}) => {
|
|
505
|
+
const tf = editor.getTransforms(AIPlugin);
|
|
506
|
+
const matchesTrigger = (text) => {
|
|
507
|
+
const { trigger } = getOptions();
|
|
508
|
+
if (trigger instanceof RegExp) {
|
|
509
|
+
return trigger.test(text);
|
|
510
|
+
}
|
|
511
|
+
if (Array.isArray(trigger)) {
|
|
512
|
+
return trigger.includes(text);
|
|
513
|
+
}
|
|
514
|
+
return text === trigger;
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
transforms: {
|
|
518
|
+
insertText(text, options) {
|
|
519
|
+
const { triggerPreviousCharPattern, triggerQuery } = getOptions();
|
|
520
|
+
const fn = () => {
|
|
521
|
+
if (!editor.selection || !matchesTrigger(text) || triggerQuery && !triggerQuery(editor)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const previousChar = editor.api.string(
|
|
525
|
+
editor.api.range("before", editor.selection)
|
|
526
|
+
);
|
|
527
|
+
const matchesPreviousCharPattern = triggerPreviousCharPattern?.test(previousChar);
|
|
528
|
+
if (!matchesPreviousCharPattern) return;
|
|
529
|
+
const nodeEntry = editor.api.block({ highest: true });
|
|
530
|
+
if (!nodeEntry || !editor.api.isEmpty(nodeEntry[0])) return;
|
|
531
|
+
api.aiChat.show();
|
|
532
|
+
return true;
|
|
533
|
+
};
|
|
534
|
+
if (fn()) return;
|
|
535
|
+
return insertText(text, options);
|
|
536
|
+
},
|
|
537
|
+
normalizeNode(entry) {
|
|
538
|
+
const [node, path] = entry;
|
|
539
|
+
if (node[import_platejs10.KEYS.ai] && !getOptions().open) {
|
|
540
|
+
tf.ai.removeMarks({ at: path });
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (import_platejs10.ElementApi.isElement(node) && node.type === type && !getOptions().open) {
|
|
544
|
+
editor.getTransforms(AIChatPlugin).aiChat.removeAnchor({ at: path });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
return normalizeNode(entry);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// src/react/ai-chat/AIChatPlugin.ts
|
|
554
|
+
var AIChatPlugin = (0, import_react10.createTPlatePlugin)({
|
|
555
|
+
key: import_platejs11.KEYS.aiChat,
|
|
556
|
+
dependencies: ["ai"],
|
|
557
|
+
node: {
|
|
558
|
+
isElement: true
|
|
559
|
+
},
|
|
560
|
+
options: {
|
|
561
|
+
_blockChunks: "",
|
|
562
|
+
_blockPath: null,
|
|
563
|
+
aiEditor: null,
|
|
564
|
+
chat: { messages: [] },
|
|
565
|
+
experimental_lastTextId: null,
|
|
566
|
+
mode: "chat",
|
|
567
|
+
open: false,
|
|
568
|
+
streaming: false,
|
|
569
|
+
trigger: " ",
|
|
570
|
+
triggerPreviousCharPattern: /^\s?$/,
|
|
571
|
+
promptTemplate: () => "{prompt}",
|
|
572
|
+
systemTemplate: () => {
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}).overrideEditor(withAIChat).extendApi(({ editor, getOption, getOptions, setOption, type }) => {
|
|
576
|
+
return {
|
|
577
|
+
reset: (0, import_platejs11.bindFirst)(resetAIChat, editor),
|
|
578
|
+
submit: (0, import_platejs11.bindFirst)(submitAIChat, editor),
|
|
579
|
+
node: (options = {}) => {
|
|
580
|
+
const { anchor = false, streaming = false, ...rest } = options;
|
|
581
|
+
if (anchor) {
|
|
582
|
+
return editor.api.node({
|
|
583
|
+
at: [],
|
|
584
|
+
match: (n) => import_platejs11.ElementApi.isElement(n) && n.type === type,
|
|
585
|
+
...rest
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (streaming) {
|
|
589
|
+
if (!getOption("streaming")) return;
|
|
590
|
+
const path = getOption("_blockPath");
|
|
591
|
+
if (!path) return;
|
|
592
|
+
return editor.api.node({
|
|
593
|
+
at: path,
|
|
594
|
+
mode: "lowest",
|
|
595
|
+
reverse: true,
|
|
596
|
+
match: (t) => !!t.ai,
|
|
597
|
+
...rest
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return editor.api.node({
|
|
601
|
+
match: (n) => n.ai,
|
|
602
|
+
...rest
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
reload: () => {
|
|
606
|
+
const { chat, mode } = getOptions();
|
|
607
|
+
if (mode === "insert") {
|
|
608
|
+
editor.getTransforms(AIPlugin).ai.undo();
|
|
609
|
+
}
|
|
610
|
+
void chat.reload?.({
|
|
611
|
+
body: {
|
|
612
|
+
system: getEditorPrompt(editor, {
|
|
613
|
+
promptTemplate: getOptions().systemTemplate
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
},
|
|
618
|
+
stop: () => {
|
|
619
|
+
setOption("streaming", false);
|
|
620
|
+
getOptions().chat.stop?.();
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}).extendApi(({ api, editor, getOptions, setOption, tf, type }) => ({
|
|
624
|
+
hide: () => {
|
|
625
|
+
api.aiChat.reset();
|
|
626
|
+
setOption("open", false);
|
|
627
|
+
if (editor.getOption(import_react9.BlockSelectionPlugin, "isSelectingSome")) {
|
|
628
|
+
} else {
|
|
629
|
+
editor.tf.focus();
|
|
630
|
+
}
|
|
631
|
+
const lastBatch = editor.history.undos.at(-1);
|
|
632
|
+
if (lastBatch?.ai) {
|
|
633
|
+
delete lastBatch.ai;
|
|
634
|
+
}
|
|
635
|
+
tf.aiChat.removeAnchor();
|
|
636
|
+
},
|
|
637
|
+
show: () => {
|
|
638
|
+
api.aiChat.reset();
|
|
639
|
+
getOptions().chat.setMessages?.([]);
|
|
640
|
+
setOption("open", true);
|
|
641
|
+
}
|
|
642
|
+
})).extendTransforms(({ editor }) => ({
|
|
643
|
+
accept: (0, import_platejs11.bindFirst)(acceptAIChat, editor),
|
|
644
|
+
insertBelow: (0, import_platejs11.bindFirst)(insertBelowAIChat, editor),
|
|
645
|
+
removeAnchor: (0, import_platejs11.bindFirst)(removeAnchorAIChat, editor),
|
|
646
|
+
replaceSelection: (0, import_platejs11.bindFirst)(replaceSelectionAIChat, editor)
|
|
647
|
+
}));
|
|
648
|
+
|
|
649
|
+
// src/react/ai-chat/hooks/useAIChatEditor.ts
|
|
650
|
+
var import_react11 = require("react");
|
|
651
|
+
var import_markdown2 = require("@platejs/markdown");
|
|
652
|
+
var import_react12 = require("platejs/react");
|
|
653
|
+
var useAIChatEditor = (editor, content, { parser } = {}) => {
|
|
654
|
+
const { setOption } = (0, import_react12.useEditorPlugin)(AIChatPlugin);
|
|
655
|
+
editor.children = (0, import_react11.useMemo)(
|
|
656
|
+
() => {
|
|
657
|
+
return editor.getApi(import_markdown2.MarkdownPlugin).markdown.deserialize(content, {
|
|
658
|
+
memoize: true,
|
|
659
|
+
parser
|
|
660
|
+
});
|
|
661
|
+
},
|
|
662
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
663
|
+
[content]
|
|
664
|
+
);
|
|
665
|
+
(0, import_react11.useEffect)(() => {
|
|
666
|
+
setOption("aiEditor", editor);
|
|
667
|
+
}, [editor, setOption]);
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// src/react/ai-chat/hooks/useChatChunk.ts
|
|
671
|
+
var import_react14 = require("react");
|
|
672
|
+
var import_platejs12 = require("platejs");
|
|
673
|
+
var import_react15 = require("platejs/react");
|
|
674
|
+
|
|
675
|
+
// src/react/ai-chat/utils/getLastAssistantMessage.ts
|
|
676
|
+
var import_react13 = require("platejs/react");
|
|
677
|
+
function getLastAssistantMessage(editor) {
|
|
678
|
+
const messages = editor.getOptions(AIChatPlugin).chat.messages;
|
|
679
|
+
return messages?.findLast((message) => message.role === "assistant");
|
|
680
|
+
}
|
|
681
|
+
function useLastAssistantMessage() {
|
|
682
|
+
const chat = (0, import_react13.usePluginOption)(AIChatPlugin, "chat");
|
|
683
|
+
return chat.messages?.findLast((message) => message.role === "assistant");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/react/ai-chat/hooks/useChatChunk.ts
|
|
687
|
+
var useChatChunk = ({
|
|
688
|
+
onChunk,
|
|
689
|
+
onFinish
|
|
690
|
+
}) => {
|
|
691
|
+
const { isLoading } = (0, import_react15.usePluginOption)(
|
|
692
|
+
{ key: import_platejs12.KEYS.aiChat },
|
|
693
|
+
"chat"
|
|
694
|
+
);
|
|
695
|
+
const content = useLastAssistantMessage()?.content;
|
|
696
|
+
const insertedTextRef = (0, import_react14.useRef)("");
|
|
697
|
+
const prevIsLoadingRef = (0, import_react14.useRef)(isLoading);
|
|
698
|
+
(0, import_react14.useEffect)(() => {
|
|
699
|
+
if (!isLoading) {
|
|
700
|
+
insertedTextRef.current = "";
|
|
701
|
+
}
|
|
702
|
+
if (prevIsLoadingRef.current && !isLoading) {
|
|
703
|
+
onFinish?.({ content: content ?? "" });
|
|
704
|
+
}
|
|
705
|
+
prevIsLoadingRef.current = isLoading;
|
|
706
|
+
}, [isLoading]);
|
|
707
|
+
(0, import_react14.useEffect)(() => {
|
|
708
|
+
if (!content) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const chunk = content.slice(insertedTextRef.current.length);
|
|
712
|
+
const nodes = [];
|
|
713
|
+
if (chunk) {
|
|
714
|
+
const isFirst = insertedTextRef.current === "";
|
|
715
|
+
nodes.push({ text: chunk });
|
|
716
|
+
onChunk({
|
|
717
|
+
chunk,
|
|
718
|
+
isFirst,
|
|
719
|
+
nodes,
|
|
720
|
+
text: content
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
insertedTextRef.current = content;
|
|
724
|
+
}, [content]);
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/react/ai-chat/hooks/useEditorChat.ts
|
|
728
|
+
var import_react16 = require("react");
|
|
729
|
+
var import_react17 = require("@platejs/selection/react");
|
|
730
|
+
var import_react18 = require("platejs/react");
|
|
731
|
+
var useEditorChat = ({
|
|
732
|
+
chat,
|
|
733
|
+
onOpenBlockSelection,
|
|
734
|
+
onOpenChange,
|
|
735
|
+
onOpenCursor,
|
|
736
|
+
onOpenSelection
|
|
737
|
+
}) => {
|
|
738
|
+
const { editor, setOption } = (0, import_react18.useEditorPlugin)(AIChatPlugin);
|
|
739
|
+
const open = (0, import_react18.usePluginOption)(AIChatPlugin, "open");
|
|
740
|
+
(0, import_react16.useEffect)(() => {
|
|
741
|
+
setOption("chat", chat);
|
|
742
|
+
}, [chat.input, chat.messages, chat.isLoading, chat.data, chat.error]);
|
|
743
|
+
(0, import_react16.useEffect)(() => {
|
|
744
|
+
onOpenChange?.(open);
|
|
745
|
+
if (open) {
|
|
746
|
+
if (onOpenBlockSelection) {
|
|
747
|
+
const blockSelectionApi = editor.getApi(import_react17.BlockSelectionPlugin).blockSelection;
|
|
748
|
+
const isBlockSelecting = editor.getOption(
|
|
749
|
+
import_react17.BlockSelectionPlugin,
|
|
750
|
+
"isSelectingSome"
|
|
751
|
+
);
|
|
752
|
+
if (isBlockSelecting) {
|
|
753
|
+
onOpenBlockSelection(blockSelectionApi.getNodes());
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (onOpenCursor && editor.api.isCollapsed()) {
|
|
758
|
+
onOpenCursor();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (onOpenSelection && editor.api.isExpanded()) {
|
|
762
|
+
onOpenSelection();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}, [open]);
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/react/copilot/CopilotPlugin.tsx
|
|
770
|
+
var import_markdown6 = require("@platejs/markdown");
|
|
771
|
+
var import_debounce = __toESM(require("lodash/debounce.js"));
|
|
772
|
+
var import_platejs18 = require("platejs");
|
|
773
|
+
var import_react23 = require("platejs/react");
|
|
774
|
+
|
|
775
|
+
// src/react/copilot/renderCopilotBelowNodes.tsx
|
|
776
|
+
var import_react19 = __toESM(require("react"));
|
|
777
|
+
var import_platejs13 = require("platejs");
|
|
778
|
+
var import_react20 = require("platejs/react");
|
|
779
|
+
var renderCopilotBelowNodes = ({
|
|
780
|
+
editor
|
|
781
|
+
}) => {
|
|
782
|
+
const copilot = (0, import_react20.getEditorPlugin)(editor, {
|
|
783
|
+
key: import_platejs13.KEYS.copilot
|
|
784
|
+
});
|
|
785
|
+
const { renderGhostText: GhostText } = copilot.getOptions();
|
|
786
|
+
if (!GhostText) return;
|
|
787
|
+
return ({ children }) => {
|
|
788
|
+
return /* @__PURE__ */ import_react19.default.createElement(import_react19.default.Fragment, null, children, /* @__PURE__ */ import_react19.default.createElement(GhostText, null));
|
|
789
|
+
};
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/react/copilot/transforms/acceptCopilot.ts
|
|
793
|
+
var import_markdown3 = require("@platejs/markdown");
|
|
794
|
+
var import_platejs14 = require("platejs");
|
|
795
|
+
var acceptCopilot = (editor) => {
|
|
796
|
+
const { suggestionText } = editor.getOptions({
|
|
797
|
+
key: import_platejs14.KEYS.copilot
|
|
798
|
+
});
|
|
799
|
+
if (!suggestionText?.length) return false;
|
|
800
|
+
editor.tf.insertFragment((0, import_markdown3.deserializeInlineMd)(editor, suggestionText));
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// src/react/copilot/transforms/acceptCopilotNextWord.ts
|
|
804
|
+
var import_markdown4 = require("@platejs/markdown");
|
|
805
|
+
var import_platejs16 = require("platejs");
|
|
806
|
+
var import_react22 = require("platejs/react");
|
|
807
|
+
|
|
808
|
+
// src/react/copilot/utils/callCompletionApi.ts
|
|
809
|
+
var getOriginalFetch = () => fetch;
|
|
810
|
+
async function callCompletionApi({
|
|
811
|
+
api = "/api/completion",
|
|
812
|
+
body,
|
|
813
|
+
credentials,
|
|
814
|
+
fetch: fetch2 = getOriginalFetch(),
|
|
815
|
+
headers,
|
|
816
|
+
prompt,
|
|
817
|
+
setAbortController = () => {
|
|
818
|
+
},
|
|
819
|
+
setCompletion = () => {
|
|
820
|
+
},
|
|
821
|
+
setError = () => {
|
|
822
|
+
},
|
|
823
|
+
setLoading = () => {
|
|
824
|
+
},
|
|
825
|
+
onError,
|
|
826
|
+
onFinish,
|
|
827
|
+
onResponse
|
|
828
|
+
}) {
|
|
829
|
+
try {
|
|
830
|
+
setLoading(true);
|
|
831
|
+
setError(null);
|
|
832
|
+
const abortController = new AbortController();
|
|
833
|
+
setAbortController(abortController);
|
|
834
|
+
setCompletion("");
|
|
835
|
+
const res = await fetch2(api, {
|
|
836
|
+
body: JSON.stringify({
|
|
837
|
+
prompt,
|
|
838
|
+
...body
|
|
839
|
+
}),
|
|
840
|
+
credentials,
|
|
841
|
+
headers: {
|
|
842
|
+
"Content-Type": "application/json",
|
|
843
|
+
...headers
|
|
844
|
+
},
|
|
845
|
+
method: "POST",
|
|
846
|
+
signal: abortController.signal
|
|
847
|
+
}).catch((error) => {
|
|
848
|
+
throw error;
|
|
849
|
+
});
|
|
850
|
+
if (onResponse) {
|
|
851
|
+
await onResponse(res);
|
|
852
|
+
}
|
|
853
|
+
if (!res.ok) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
await res.text() || "Failed to fetch the chat response."
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
if (!res.body) {
|
|
859
|
+
throw new Error("The response body is empty.");
|
|
860
|
+
}
|
|
861
|
+
const { text } = await res.json();
|
|
862
|
+
if (!text) {
|
|
863
|
+
throw new Error("The response does not contain a text field.");
|
|
864
|
+
}
|
|
865
|
+
setCompletion(text);
|
|
866
|
+
if (onFinish) {
|
|
867
|
+
onFinish(prompt, text);
|
|
868
|
+
}
|
|
869
|
+
setAbortController(null);
|
|
870
|
+
return text;
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (error.name === "AbortError") {
|
|
873
|
+
setAbortController(null);
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
if (error instanceof Error && onError) {
|
|
877
|
+
onError(error);
|
|
878
|
+
}
|
|
879
|
+
setError(error);
|
|
880
|
+
} finally {
|
|
881
|
+
setLoading(false);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/react/copilot/utils/getNextWord.ts
|
|
886
|
+
var getNextWord = ({ text }) => {
|
|
887
|
+
if (!text) return { firstWord: "", remainingText: "" };
|
|
888
|
+
const nonSpaceMatch = /^\s*(\S)/.exec(text);
|
|
889
|
+
if (!nonSpaceMatch) return { firstWord: "", remainingText: "" };
|
|
890
|
+
const firstNonSpaceChar = nonSpaceMatch[1];
|
|
891
|
+
const isCJKChar = /[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/.test(
|
|
892
|
+
firstNonSpaceChar
|
|
893
|
+
);
|
|
894
|
+
let firstWord, remainingText;
|
|
895
|
+
if (isCJKChar) {
|
|
896
|
+
const match = /^(\s*)([\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF])([\u3000-\u303F\uFF00-\uFFEF])?/.exec(
|
|
897
|
+
text
|
|
898
|
+
);
|
|
899
|
+
if (match) {
|
|
900
|
+
const [fullMatch, spaces = "", char = "", punctuation = ""] = match;
|
|
901
|
+
firstWord = spaces + char + punctuation;
|
|
902
|
+
remainingText = text.slice(firstWord.length);
|
|
903
|
+
} else {
|
|
904
|
+
firstWord = "";
|
|
905
|
+
remainingText = text;
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
const match = /^(\s*\S+?)(?=[\s\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]|$)/.exec(
|
|
909
|
+
text
|
|
910
|
+
);
|
|
911
|
+
if (match) {
|
|
912
|
+
firstWord = match[0];
|
|
913
|
+
remainingText = text.slice(firstWord.length);
|
|
914
|
+
} else {
|
|
915
|
+
firstWord = text;
|
|
916
|
+
remainingText = "";
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return { firstWord, remainingText };
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// src/react/copilot/utils/triggerCopilotSuggestion.ts
|
|
923
|
+
var import_platejs15 = require("platejs");
|
|
924
|
+
var import_react21 = require("platejs/react");
|
|
925
|
+
var triggerCopilotSuggestion = async (editor) => {
|
|
926
|
+
const { api, getOptions, setOption } = (0, import_react21.getEditorPlugin)(
|
|
927
|
+
editor,
|
|
928
|
+
{
|
|
929
|
+
key: import_platejs15.KEYS.copilot
|
|
930
|
+
}
|
|
931
|
+
);
|
|
932
|
+
const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
|
|
933
|
+
if (isLoading || editor.getOptions({ key: import_platejs15.KEYS.aiChat }).chat?.isLoading) {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
if (!triggerQuery({ editor })) return false;
|
|
937
|
+
const prompt = getPrompt({ editor });
|
|
938
|
+
if (prompt.length === 0) return false;
|
|
939
|
+
api.copilot.stop();
|
|
940
|
+
await callCompletionApi({
|
|
941
|
+
prompt,
|
|
942
|
+
onFinish: (_, completion) => {
|
|
943
|
+
api.copilot.setBlockSuggestion({ text: completion });
|
|
944
|
+
},
|
|
945
|
+
...completeOptions,
|
|
946
|
+
setAbortController: (controller) => setOption("abortController", controller),
|
|
947
|
+
setCompletion: (completion) => setOption("completion", completion),
|
|
948
|
+
setError: (error) => setOption("error", error),
|
|
949
|
+
setLoading: (loading) => setOption("isLoading", loading),
|
|
950
|
+
onError: (error) => {
|
|
951
|
+
setOption("error", error);
|
|
952
|
+
completeOptions?.onError?.(error);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// src/react/copilot/utils/withoutAbort.ts
|
|
958
|
+
var withoutAbort = (editor, fn) => {
|
|
959
|
+
editor.setOption(CopilotPlugin, "shouldAbort", false);
|
|
960
|
+
fn();
|
|
961
|
+
editor.setOption(CopilotPlugin, "shouldAbort", true);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
// src/react/copilot/transforms/acceptCopilotNextWord.ts
|
|
965
|
+
var acceptCopilotNextWord = (editor) => {
|
|
966
|
+
const { api, getOptions } = (0, import_react22.getEditorPlugin)(editor, {
|
|
967
|
+
key: import_platejs16.KEYS.copilot
|
|
968
|
+
});
|
|
969
|
+
const { getNextWord: getNextWord2, suggestionText } = getOptions();
|
|
970
|
+
if (!suggestionText?.length) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
const { firstWord, remainingText } = getNextWord2({ text: suggestionText });
|
|
974
|
+
api.copilot.setBlockSuggestion({
|
|
975
|
+
text: remainingText
|
|
976
|
+
});
|
|
977
|
+
withoutAbort(editor, () => {
|
|
978
|
+
editor.tf.insertFragment((0, import_markdown4.deserializeInlineMd)(editor, firstWord));
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
// src/react/copilot/withCopilot.ts
|
|
983
|
+
var import_markdown5 = require("@platejs/markdown");
|
|
984
|
+
var import_platejs17 = require("platejs");
|
|
985
|
+
var getPatchString = (editor, operations) => {
|
|
986
|
+
let string = "";
|
|
987
|
+
for (const operation of operations) {
|
|
988
|
+
if (operation.type === "insert_node") {
|
|
989
|
+
const node = operation.node;
|
|
990
|
+
const text = (0, import_markdown5.serializeInlineMd)(editor, { value: [node] });
|
|
991
|
+
string += text;
|
|
992
|
+
} else if (operation.type === "insert_text") {
|
|
993
|
+
string += operation.text;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return string;
|
|
997
|
+
};
|
|
998
|
+
var withCopilot = ({
|
|
999
|
+
api,
|
|
1000
|
+
editor,
|
|
1001
|
+
getOptions,
|
|
1002
|
+
setOption,
|
|
1003
|
+
tf: { apply, insertText, redo, setSelection, undo, writeHistory }
|
|
1004
|
+
}) => {
|
|
1005
|
+
let prevSelection = null;
|
|
1006
|
+
return {
|
|
1007
|
+
transforms: {
|
|
1008
|
+
apply(operation) {
|
|
1009
|
+
const { shouldAbort } = getOptions();
|
|
1010
|
+
if (shouldAbort) {
|
|
1011
|
+
api.copilot.reject();
|
|
1012
|
+
}
|
|
1013
|
+
apply(operation);
|
|
1014
|
+
},
|
|
1015
|
+
insertText(text, options) {
|
|
1016
|
+
const suggestionText = getOptions().suggestionText;
|
|
1017
|
+
if (suggestionText?.startsWith(text)) {
|
|
1018
|
+
withoutAbort(editor, () => {
|
|
1019
|
+
editor.tf.withoutMerging(() => {
|
|
1020
|
+
const newText = suggestionText?.slice(text.length);
|
|
1021
|
+
setOption("suggestionText", newText);
|
|
1022
|
+
insertText(text);
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
insertText(text, options);
|
|
1028
|
+
},
|
|
1029
|
+
redo() {
|
|
1030
|
+
if (!getOptions().suggestionText) return redo();
|
|
1031
|
+
const topRedo = editor.history.redos.at(-1);
|
|
1032
|
+
const prevSuggestion = getOptions().suggestionText;
|
|
1033
|
+
if (topRedo && topRedo.shouldAbort === false && prevSuggestion) {
|
|
1034
|
+
withoutAbort(editor, () => {
|
|
1035
|
+
const shouldRemoveText = getPatchString(editor, topRedo.operations);
|
|
1036
|
+
const newText = prevSuggestion.slice(shouldRemoveText.length);
|
|
1037
|
+
setOption("suggestionText", newText);
|
|
1038
|
+
redo();
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
return redo();
|
|
1043
|
+
},
|
|
1044
|
+
setSelection(props) {
|
|
1045
|
+
setSelection(props);
|
|
1046
|
+
if (editor.selection && (!prevSelection || !import_platejs17.RangeApi.equals(prevSelection, editor.selection)) && getOptions().autoTriggerQuery({ editor }) && editor.api.isFocused()) {
|
|
1047
|
+
void api.copilot.triggerSuggestion();
|
|
1048
|
+
}
|
|
1049
|
+
prevSelection = editor.selection;
|
|
1050
|
+
},
|
|
1051
|
+
undo() {
|
|
1052
|
+
if (!getOptions().suggestionText) return undo();
|
|
1053
|
+
const lastUndos = editor.history.undos.at(-1);
|
|
1054
|
+
const oldText = getOptions().suggestionText;
|
|
1055
|
+
if (lastUndos && lastUndos.shouldAbort === false && oldText) {
|
|
1056
|
+
withoutAbort(editor, () => {
|
|
1057
|
+
const shouldInsertText = getPatchString(
|
|
1058
|
+
editor,
|
|
1059
|
+
lastUndos.operations
|
|
1060
|
+
);
|
|
1061
|
+
const newText = shouldInsertText + oldText;
|
|
1062
|
+
setOption("suggestionText", newText);
|
|
1063
|
+
undo();
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
return undo();
|
|
1068
|
+
},
|
|
1069
|
+
writeHistory(stacks, batch) {
|
|
1070
|
+
if (!getOptions().isLoading) {
|
|
1071
|
+
batch.shouldAbort = getOptions().shouldAbort;
|
|
1072
|
+
}
|
|
1073
|
+
return writeHistory(stacks, batch);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// src/react/copilot/CopilotPlugin.tsx
|
|
1080
|
+
var CopilotPlugin = (0, import_react23.createTPlatePlugin)({
|
|
1081
|
+
key: import_platejs18.KEYS.copilot,
|
|
1082
|
+
handlers: {
|
|
1083
|
+
onBlur: ({ api }) => {
|
|
1084
|
+
api.copilot.reject();
|
|
1085
|
+
},
|
|
1086
|
+
onMouseDown: ({ api }) => {
|
|
1087
|
+
api.copilot.reject();
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
options: {
|
|
1091
|
+
abortController: null,
|
|
1092
|
+
completeOptions: {},
|
|
1093
|
+
completion: "",
|
|
1094
|
+
debounceDelay: 0,
|
|
1095
|
+
error: null,
|
|
1096
|
+
getNextWord,
|
|
1097
|
+
isLoading: false,
|
|
1098
|
+
renderGhostText: null,
|
|
1099
|
+
shouldAbort: true,
|
|
1100
|
+
suggestionNodeId: null,
|
|
1101
|
+
suggestionText: null,
|
|
1102
|
+
autoTriggerQuery: ({ editor }) => {
|
|
1103
|
+
if (editor.getOptions({ key: import_platejs18.KEYS.copilot }).suggestionText) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
|
|
1107
|
+
if (isEmpty) return false;
|
|
1108
|
+
const blockAbove = editor.api.block();
|
|
1109
|
+
if (!blockAbove) return false;
|
|
1110
|
+
const blockString = import_platejs18.NodeApi.string(blockAbove[0]);
|
|
1111
|
+
return blockString.at(-1) === " ";
|
|
1112
|
+
},
|
|
1113
|
+
getPrompt: ({ editor }) => {
|
|
1114
|
+
const contextEntry = editor.api.block({ highest: true });
|
|
1115
|
+
if (!contextEntry) return "";
|
|
1116
|
+
return (0, import_markdown6.serializeMd)(editor, {
|
|
1117
|
+
value: [contextEntry[0]]
|
|
1118
|
+
});
|
|
1119
|
+
},
|
|
1120
|
+
triggerQuery: ({ editor }) => {
|
|
1121
|
+
if (editor.api.isExpanded()) return false;
|
|
1122
|
+
const isEnd = editor.api.isAt({ end: true });
|
|
1123
|
+
if (!isEnd) return false;
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}).overrideEditor(withCopilot).extendSelectors(({ getOptions }) => ({
|
|
1128
|
+
isSuggested: (id) => getOptions().suggestionNodeId === id
|
|
1129
|
+
})).extendTransforms(({ editor }) => ({
|
|
1130
|
+
accept: (0, import_platejs18.bindFirst)(acceptCopilot, editor),
|
|
1131
|
+
acceptNextWord: (0, import_platejs18.bindFirst)(acceptCopilotNextWord, editor)
|
|
1132
|
+
})).extendApi(
|
|
1133
|
+
({ api, editor, getOptions, setOption, setOptions }) => {
|
|
1134
|
+
const debounceDelay = getOptions().debounceDelay;
|
|
1135
|
+
let triggerSuggestion = (0, import_platejs18.bindFirst)(triggerCopilotSuggestion, editor);
|
|
1136
|
+
if (debounceDelay) {
|
|
1137
|
+
triggerSuggestion = (0, import_debounce.default)(
|
|
1138
|
+
(0, import_platejs18.bindFirst)(triggerCopilotSuggestion, editor),
|
|
1139
|
+
debounceDelay
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
triggerSuggestion,
|
|
1144
|
+
setBlockSuggestion: ({ id = getOptions().suggestionNodeId, text }) => {
|
|
1145
|
+
if (!id) {
|
|
1146
|
+
id = editor.api.block()[0].id;
|
|
1147
|
+
}
|
|
1148
|
+
setOptions({
|
|
1149
|
+
suggestionNodeId: id,
|
|
1150
|
+
suggestionText: text
|
|
1151
|
+
});
|
|
1152
|
+
},
|
|
1153
|
+
stop: () => {
|
|
1154
|
+
const { abortController } = getOptions();
|
|
1155
|
+
api.copilot.triggerSuggestion?.cancel();
|
|
1156
|
+
if (abortController) {
|
|
1157
|
+
abortController.abort();
|
|
1158
|
+
setOption("abortController", null);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
).extendApi(({ api, getOptions, setOptions }) => ({
|
|
1164
|
+
reject: () => {
|
|
1165
|
+
if (!getOptions().suggestionText?.length) return false;
|
|
1166
|
+
api.copilot.stop();
|
|
1167
|
+
setOptions({
|
|
1168
|
+
completion: null,
|
|
1169
|
+
suggestionNodeId: null,
|
|
1170
|
+
suggestionText: null
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
})).extend({
|
|
1174
|
+
render: {
|
|
1175
|
+
belowNodes: renderCopilotBelowNodes
|
|
1176
|
+
},
|
|
1177
|
+
shortcuts: {
|
|
1178
|
+
accept: {
|
|
1179
|
+
keys: "tab"
|
|
1180
|
+
},
|
|
1181
|
+
reject: {
|
|
1182
|
+
keys: "escape"
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1187
|
+
0 && (module.exports = {
|
|
1188
|
+
AIChatPlugin,
|
|
1189
|
+
AIPlugin,
|
|
1190
|
+
CopilotPlugin,
|
|
1191
|
+
acceptAIChat,
|
|
1192
|
+
acceptCopilot,
|
|
1193
|
+
acceptCopilotNextWord,
|
|
1194
|
+
callCompletionApi,
|
|
1195
|
+
createFormattedBlocks,
|
|
1196
|
+
getEditorPrompt,
|
|
1197
|
+
getLastAssistantMessage,
|
|
1198
|
+
getMarkdown,
|
|
1199
|
+
getNextWord,
|
|
1200
|
+
insertBelowAIChat,
|
|
1201
|
+
removeAnchorAIChat,
|
|
1202
|
+
renderCopilotBelowNodes,
|
|
1203
|
+
replaceSelectionAIChat,
|
|
1204
|
+
resetAIChat,
|
|
1205
|
+
submitAIChat,
|
|
1206
|
+
triggerCopilotSuggestion,
|
|
1207
|
+
useAIChatEditor,
|
|
1208
|
+
useChatChunk,
|
|
1209
|
+
useEditorChat,
|
|
1210
|
+
useLastAssistantMessage,
|
|
1211
|
+
withAIChat,
|
|
1212
|
+
withCopilot,
|
|
1213
|
+
withoutAbort
|
|
1214
|
+
});
|
|
1215
|
+
//# sourceMappingURL=index.js.map
|