@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.
@@ -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(/&#x20;/g, " ");
1341
- result = result.replace(/&#x200B;/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