@platejs/ai 51.1.2 → 52.0.1

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,1965 +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
- }
98
- if (isSelecting3 && config.selecting) {
99
- return config.selecting ?? config.default;
100
- }
101
- return config.default;
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: Number.POSITIVE_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
- if (!isEnd) {
197
- for (const segment of textSegments) {
198
- if (charOffset === segment.offset) {
199
- return {
200
- offset: 0,
201
- path: segment.path
202
- };
203
- }
204
- }
205
- }
206
- for (const segment of textSegments) {
207
- const segmentEnd = segment.offset + segment.text.length;
208
- if (charOffset >= segment.offset && charOffset <= segmentEnd) {
209
- const localOffset = charOffset - segment.offset;
210
- return {
211
- offset: localOffset,
212
- path: segment.path
213
- };
214
- }
215
- }
216
- const lastSegment = textSegments.at(-1);
217
- if (!lastSegment) {
218
- return { offset: 0, path: blockPath };
219
- }
220
- return {
221
- offset: lastSegment.text.length,
222
- path: lastSegment.path
223
- };
224
- };
225
- const anchor = findPoint(matchStart, false);
226
- const focus = findPoint(matchEnd, true);
227
- return {
228
- anchor,
229
- focus
230
- };
231
- }
232
-
233
- // src/react/ai/utils/aiCommentToRange.ts
234
- var aiCommentToRange = (editor, aiComment) => {
235
- const { blockId, content } = aiComment;
236
- const contentNodes = deserializeMd(editor, content);
237
- let firstBlock;
238
- const ranges = [];
239
- contentNodes.forEach((node, index) => {
240
- let currentBlock;
241
- if (index === 0) {
242
- firstBlock = editor.api.node({ id: blockId, at: [] });
243
- currentBlock = firstBlock;
244
- } else {
245
- if (!firstBlock) return;
246
- const [_, firstBlockPath] = firstBlock;
247
- const blockPath = [firstBlockPath[0] + index];
248
- currentBlock = editor.api.node(blockPath);
249
- }
250
- if (!currentBlock) return;
251
- const range = findTextRangeInBlock({
252
- block: currentBlock,
253
- findText: NodeApi2.string(node)
254
- });
255
- if (!range) return;
256
- ranges.push(range);
257
- });
258
- if (ranges.length === 0) return;
259
- if (ranges.length > 1) {
260
- const startRange = ranges[0];
261
- const endRange = ranges.at(-1);
262
- return {
263
- anchor: startRange.anchor,
264
- focus: endRange.focus
265
- };
266
- }
267
- if (ranges.length === 1) {
268
- return ranges[0];
269
- }
270
- };
271
-
272
- // src/react/ai-chat/AIChatPlugin.ts
273
- import { BlockSelectionPlugin as BlockSelectionPlugin5 } from "@platejs/selection/react";
274
- import {
275
- bindFirst as bindFirst2,
276
- ElementApi as ElementApi4,
277
- getPluginType as getPluginType3,
278
- KEYS as KEYS12
279
- } from "platejs";
280
- import { createTPlatePlugin } from "platejs/react";
281
-
282
- // src/react/ai-chat/transforms/acceptAIChat.ts
283
- import { KEYS as KEYS4 } from "platejs";
284
- import { getEditorPlugin } from "platejs/react";
285
-
286
- // src/react/ai-chat/utils/acceptAISuggestions.ts
287
- import {
288
- acceptSuggestion,
289
- getSuggestionKey,
290
- getTransientSuggestionKey as getTransientSuggestionKey2
291
- } from "@platejs/suggestion";
292
- import { SuggestionPlugin } from "@platejs/suggestion/react";
293
- var acceptAISuggestions = (editor) => {
294
- const suggestions = editor.getApi(SuggestionPlugin).suggestion.nodes({
295
- transient: true
296
- });
297
- suggestions.forEach(([suggestionNode]) => {
298
- const suggestionData = editor.getApi(SuggestionPlugin).suggestion.suggestionData(suggestionNode);
299
- if (!suggestionData) return;
300
- const description = {
301
- createdAt: new Date(suggestionData.createdAt),
302
- keyId: getSuggestionKey(suggestionData.id),
303
- suggestionId: suggestionData.id,
304
- type: suggestionData.type,
305
- userId: suggestionData.userId
306
- };
307
- acceptSuggestion(editor, description);
308
- });
309
- editor.tf.unsetNodes([getTransientSuggestionKey2()], {
310
- at: [],
311
- mode: "all",
312
- match: (n) => !!n[getTransientSuggestionKey2()]
313
- });
314
- };
315
-
316
- // src/react/ai-chat/transforms/acceptAIChat.ts
317
- var acceptAIChat = (editor) => {
318
- const mode = editor.getOption(AIChatPlugin, "mode");
319
- if (mode === "insert") {
320
- const { tf } = getEditorPlugin(editor, AIPlugin);
321
- const api = editor.getApi({ key: KEYS4.ai });
322
- const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
323
- withAIBatch(editor, () => {
324
- tf.ai.removeMarks();
325
- editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
326
- });
327
- api.aiChat.hide();
328
- editor.tf.focus();
329
- const focusPoint = editor.api.end(lastAINodePath);
330
- editor.tf.setSelection({
331
- anchor: focusPoint,
332
- focus: focusPoint
333
- });
334
- }
335
- if (mode === "chat") {
336
- withAIBatch(editor, () => {
337
- acceptAISuggestions(editor);
338
- });
339
- editor.getApi(AIChatPlugin).aiChat.hide();
340
- }
341
- };
342
-
343
- // src/react/ai-chat/transforms/insertBelowAIChat.ts
344
- import { BlockSelectionPlugin as BlockSelectionPlugin4 } from "@platejs/selection/react";
345
- import cloneDeep2 from "lodash/cloneDeep.js";
346
- import { KEYS as KEYS9, PathApi, RangeApi } from "platejs";
347
-
348
- // src/react/ai-chat/utils/applyAISuggestions.ts
349
- import { deserializeMd as deserializeMd2 } from "@platejs/markdown";
350
- import { BlockSelectionPlugin } from "@platejs/selection/react";
351
- import {
352
- diffToSuggestions,
353
- getTransientSuggestionKey as getTransientSuggestionKey3,
354
- SkipSuggestionDeletes
355
- } from "@platejs/suggestion";
356
- import {
357
- ElementApi,
358
- KEYS as KEYS5,
359
- nanoid,
360
- TextApi as TextApi2
361
- } from "platejs";
362
- var applyAISuggestions = (editor, content) => {
363
- editor.getApi({ key: KEYS5.cursorOverlay })?.cursorOverlay?.removeCursor("selection");
364
- const isBlockSelecting = editor.getOption(
365
- BlockSelectionPlugin,
366
- "isSelectingSome"
367
- );
368
- const { chatNodes } = editor.getOptions(AIChatPlugin);
369
- if (isBlockSelecting || editor.api.blocks({ mode: "highest" }).length > 1) {
370
- const setReplaceIds = (ids) => {
371
- editor.setOption(AIChatPlugin, "_replaceIds", ids);
372
- };
373
- if (editor.getOption(AIChatPlugin, "_replaceIds").length === 0) {
374
- setReplaceIds(chatNodes.map((node) => node.id));
375
- }
376
- const diffNodes = getDiffNodes(editor, content);
377
- const replaceNodes = Array.from(
378
- editor.api.nodes({
379
- at: [],
380
- match: (n) => ElementApi.isElement(n) && editor.getOption(AIChatPlugin, "_replaceIds").includes(n.id)
381
- })
382
- );
383
- replaceNodes.forEach(([node, path], index) => {
384
- const replaceNode = node;
385
- const diffNode = diffNodes[index];
386
- const isSameString = SkipSuggestionDeletes(editor, replaceNode) === SkipSuggestionDeletes(editor, diffNode);
387
- const isSameSuggestion = replaceNode.suggestion?.type === diffNode.suggestion?.type;
388
- if (index === replaceNodes.length - 1 && diffNodes.length > replaceNodes.length) {
389
- editor.tf.replaceNodes(diffNodes.slice(index), {
390
- at: path
391
- });
392
- }
393
- if (isSameString && isSameSuggestion && node.id === diffNode.id) {
394
- return;
395
- }
396
- editor.tf.replaceNodes(diffNode, {
397
- at: path
398
- });
399
- });
400
- editor.getApi(BlockSelectionPlugin).blockSelection.set(diffNodes.map((node) => node.id));
401
- setReplaceIds(diffNodes.map((node) => node.id));
402
- } else {
403
- const diffNodes = getDiffNodes(editor, content);
404
- editor.tf.insertFragment(diffNodes);
405
- const nodes = Array.from(
406
- editor.api.nodes({
407
- at: [],
408
- mode: "lowest",
409
- match: (n) => TextApi2.isText(n) && !!n[getTransientSuggestionKey3()]
410
- })
411
- );
412
- const range = editor.api.nodesRange(nodes);
413
- editor.tf.setSelection(range);
414
- return;
415
- }
416
- };
417
- var withProps = (diffNodes, chatNodes) => diffNodes.map((node, index) => {
418
- if (!ElementApi.isElement(node)) return node;
419
- const originalNode = chatNodes?.[index];
420
- return {
421
- ...node,
422
- ...originalNode ?? { id: nanoid() },
423
- children: node.children
424
- };
425
- });
426
- var withTransient = (diffNodes) => diffNodes.map((node) => {
427
- if (TextApi2.isText(node)) {
428
- return {
429
- ...node,
430
- [getTransientSuggestionKey3()]: true
431
- };
432
- }
433
- return {
434
- ...node,
435
- children: withTransient(node.children),
436
- [getTransientSuggestionKey3()]: true
437
- };
438
- });
439
- var withoutSuggestionAndComments = (nodes) => nodes.map((node) => {
440
- if (TextApi2.isText(node)) {
441
- if (node[KEYS5.suggestion] || node[KEYS5.comment]) {
442
- return {
443
- text: node.text
444
- };
445
- }
446
- return node;
447
- }
448
- if (ElementApi.isElement(node)) {
449
- if (node[KEYS5.suggestion]) {
450
- const nodeWithoutSuggestion = {};
451
- Object.keys(node).forEach((key) => {
452
- if (key !== KEYS5.suggestion && !key.startsWith(KEYS5.suggestion)) {
453
- nodeWithoutSuggestion[key] = node[key];
454
- }
455
- });
456
- return {
457
- ...nodeWithoutSuggestion,
458
- children: withoutSuggestionAndComments(node.children)
459
- };
460
- }
461
- return {
462
- ...node,
463
- children: withoutSuggestionAndComments(node.children)
464
- };
465
- }
466
- return node;
467
- });
468
- var getDiffNodes = (editor, aiContent) => {
469
- const chatNodes = withoutSuggestionAndComments(
470
- editor.getOption(AIChatPlugin, "chatNodes")
471
- );
472
- const aiNodes = withProps(deserializeMd2(editor, aiContent), chatNodes);
473
- const diffNodes = withTransient(
474
- diffToSuggestions(editor, chatNodes, aiNodes, {
475
- ignoreProps: ["id", "listStart"]
476
- })
477
- );
478
- return diffNodes;
479
- };
480
-
481
- // src/react/ai-chat/utils/getLastAssistantMessage.ts
482
- import { usePluginOption } from "platejs/react";
483
- function getLastAssistantMessage(editor) {
484
- const messages = editor.getOptions(AIChatPlugin).chat.messages;
485
- return messages?.findLast((message) => message.role === "assistant");
486
- }
487
- function useLastAssistantMessage() {
488
- const toolName = usePluginOption(AIChatPlugin, "toolName");
489
- const chat = usePluginOption(AIChatPlugin, "chat");
490
- if (toolName === "comment") return;
491
- return chat.messages?.findLast((message) => message.role === "assistant");
492
- }
493
-
494
- // src/react/ai-chat/utils/rejectAISuggestions.ts
495
- import {
496
- getSuggestionKey as getSuggestionKey2,
497
- getTransientSuggestionKey as getTransientSuggestionKey4,
498
- rejectSuggestion
499
- } 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: { 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
- reset: bindFirst2(resetAIChat, editor),
911
- submit: bindFirst2(submitAIChat, editor),
912
- node: (options = {}) => {
913
- const { anchor = false, streaming = false, ...rest } = options;
914
- if (anchor) {
915
- return editor.api.node({
916
- at: [],
917
- match: (n) => ElementApi4.isElement(n) && n.type === type,
918
- ...rest
919
- });
920
- }
921
- if (streaming) {
922
- if (!getOption("streaming")) return;
923
- const path = getOption("_blockPath");
924
- if (!path) return;
925
- return editor.api.node({
926
- at: path,
927
- mode: "lowest",
928
- reverse: true,
929
- match: (t) => !!t[getPluginType3(editor, KEYS12.ai)],
930
- ...rest
931
- });
932
- }
933
- return editor.api.node({
934
- match: (n) => n[getPluginType3(editor, KEYS12.ai)],
935
- ...rest
936
- });
937
- },
938
- reload: () => {
939
- const { chat, chatNodes, chatSelection } = getOptions();
940
- editor.getTransforms(AIPlugin).ai.undo();
941
- if (chatSelection) {
942
- editor.tf.setSelection(chatSelection);
943
- } else {
944
- editor.getApi(BlockSelectionPlugin5).blockSelection.set(chatNodes.map((node) => node.id));
945
- }
946
- const blocks = editor.getApi(BlockSelectionPlugin5).blockSelection.getNodes();
947
- const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
948
- const ctx = {
949
- children: editor.children,
950
- selection: selection ?? null,
951
- toolName: getOption("toolName")
952
- };
953
- void chat.regenerate?.({
954
- body: {
955
- ctx
956
- }
957
- });
958
- },
959
- stop: () => {
960
- setOption("streaming", false);
961
- getOptions().chat.stop?.();
962
- }
963
- })).extendApi(({ api, editor, getOptions, setOption, tf }) => ({
964
- hide: ({
965
- focus = true,
966
- undo = true
967
- } = {}) => {
968
- api.aiChat.reset({ undo });
969
- setOption("open", false);
970
- if (focus) {
971
- if (editor.getOption(BlockSelectionPlugin5, "isSelectingSome")) {
972
- editor.getApi(BlockSelectionPlugin5).blockSelection.focus();
973
- } else {
974
- editor.tf.focus();
975
- }
976
- }
977
- const lastBatch = editor.history.undos.at(-1);
978
- if (lastBatch?.ai) {
979
- lastBatch.ai = void 0;
980
- }
981
- tf.aiChat.removeAnchor();
982
- },
983
- show: () => {
984
- api.aiChat.reset();
985
- setOption("toolName", null);
986
- getOptions().chat.setMessages?.([]);
987
- setOption("open", true);
988
- }
989
- })).extendTransforms(({ editor }) => ({
990
- accept: bindFirst2(acceptAIChat, editor),
991
- insertBelow: bindFirst2(insertBelowAIChat, editor),
992
- removeAnchor: bindFirst2(removeAnchorAIChat, editor),
993
- replaceSelection: bindFirst2(replaceSelectionAIChat, editor)
994
- }));
995
-
996
- // src/react/ai-chat/hooks/useAIChatEditor.ts
997
- import { useEffect, useMemo } from "react";
998
- import { MarkdownPlugin } from "@platejs/markdown";
999
- import { useEditorPlugin } from "platejs/react";
1000
- var useAIChatEditor = (editor, content, { parser } = {}) => {
1001
- const { setOption } = useEditorPlugin(AIChatPlugin);
1002
- editor.children = useMemo(
1003
- () => editor.getApi(MarkdownPlugin).markdown.deserialize(content, {
1004
- memoize: true,
1005
- parser
1006
- }),
1007
- // eslint-disable-next-line react-hooks/exhaustive-deps
1008
- [content]
1009
- );
1010
- useEffect(() => {
1011
- setOption("aiEditor", editor);
1012
- }, [editor, setOption]);
1013
- };
1014
-
1015
- // src/react/ai-chat/hooks/useChatChunk.ts
1016
- import { useEffect as useEffect2, useRef } from "react";
1017
- import { KEYS as KEYS13 } from "platejs";
1018
- import { usePluginOption as usePluginOption2 } from "platejs/react";
1019
- var useChatChunk = ({
1020
- onChunk,
1021
- onFinish
1022
- }) => {
1023
- const { status } = usePluginOption2(
1024
- { key: KEYS13.aiChat },
1025
- "chat"
1026
- );
1027
- const isLoading = status === "streaming" || status === "submitted";
1028
- const content = useLastAssistantMessage()?.parts.find(
1029
- (part) => part.type === "text"
1030
- )?.text;
1031
- const insertedTextRef = useRef("");
1032
- const prevIsLoadingRef = useRef(isLoading);
1033
- useEffect2(() => {
1034
- if (!isLoading) {
1035
- insertedTextRef.current = "";
1036
- }
1037
- if (prevIsLoadingRef.current && !isLoading) {
1038
- onFinish?.({ content: content ?? "" });
1039
- }
1040
- prevIsLoadingRef.current = isLoading;
1041
- }, [isLoading]);
1042
- useEffect2(() => {
1043
- if (!content) {
1044
- return;
1045
- }
1046
- const chunk = content.slice(insertedTextRef.current.length);
1047
- const nodes = [];
1048
- if (chunk) {
1049
- const isFirst = insertedTextRef.current === "";
1050
- nodes.push({ text: chunk });
1051
- onChunk({
1052
- chunk,
1053
- isFirst,
1054
- nodes,
1055
- text: content
1056
- });
1057
- }
1058
- insertedTextRef.current = content;
1059
- }, [content]);
1060
- };
1061
-
1062
- // src/react/ai-chat/hooks/useEditorChat.ts
1063
- import { useEffect as useEffect3 } from "react";
1064
- import { BlockSelectionPlugin as BlockSelectionPlugin6 } from "@platejs/selection/react";
1065
- import { useEditorPlugin as useEditorPlugin2, usePluginOption as usePluginOption3 } from "platejs/react";
1066
- var useEditorChat = ({
1067
- onOpenBlockSelection,
1068
- onOpenChange,
1069
- onOpenCursor,
1070
- onOpenSelection
1071
- }) => {
1072
- const { editor } = useEditorPlugin2(AIChatPlugin);
1073
- const open = usePluginOption3(AIChatPlugin, "open");
1074
- useEffect3(() => {
1075
- onOpenChange?.(open);
1076
- if (open) {
1077
- if (onOpenBlockSelection) {
1078
- const blockSelectionApi = editor.getApi(BlockSelectionPlugin6).blockSelection;
1079
- const isBlockSelecting = editor.getOption(
1080
- BlockSelectionPlugin6,
1081
- "isSelectingSome"
1082
- );
1083
- if (isBlockSelecting) {
1084
- onOpenBlockSelection(blockSelectionApi.getNodes());
1085
- return;
1086
- }
1087
- }
1088
- if (onOpenCursor && editor.api.isCollapsed()) {
1089
- onOpenCursor();
1090
- return;
1091
- }
1092
- if (onOpenSelection && editor.api.isExpanded()) {
1093
- onOpenSelection();
1094
- return;
1095
- }
1096
- }
1097
- }, [open]);
1098
- };
1099
-
1100
- // src/react/ai-chat/streaming/streamDeserializeInlineMd.ts
1101
- import { MarkdownPlugin as MarkdownPlugin2 } from "@platejs/markdown";
1102
- var streamDeserializeInlineMd = (editor, text, options) => editor.getApi(MarkdownPlugin2).markdown.deserializeInline(text, options);
1103
-
1104
- // src/react/ai-chat/streaming/streamDeserializeMd.ts
1105
- import { MarkdownPlugin as MarkdownPlugin3 } from "@platejs/markdown";
1106
- import { getPluginType as getPluginType4, KEYS as KEYS15, TextApi as TextApi4 } from "platejs";
1107
-
1108
- // src/react/ai-chat/streaming/utils/utils.ts
1109
- var getChunkTrimmed = (chunk, {
1110
- direction = "right"
1111
- } = {}) => {
1112
- const str = direction === "right" ? chunk.trimEnd() : chunk.trimStart();
1113
- if (direction === "right") {
1114
- return chunk.slice(str.length);
1115
- }
1116
- return chunk.slice(0, chunk.length - str.length);
1117
- };
1118
- function isCompleteCodeBlock(str) {
1119
- const trimmed = str.trim();
1120
- const startsWithCodeBlock = trimmed.startsWith("```");
1121
- const endsWithCodeBlock = trimmed.endsWith("```");
1122
- return startsWithCodeBlock && endsWithCodeBlock;
1123
- }
1124
- function isCompleteMath(str) {
1125
- const trimmed = str.trim();
1126
- const startsWithMath = trimmed.startsWith("$$");
1127
- const endsWithMath = trimmed.endsWith("$$");
1128
- return startsWithMath && endsWithMath;
1129
- }
1130
-
1131
- // src/react/ai-chat/streaming/utils/escapeInput.ts
1132
- var escapeInput = (data) => {
1133
- let res = data;
1134
- if (data.startsWith("$$") && !data.startsWith("$$\n") && !isCompleteMath(data)) {
1135
- res = data.replace("$$", String.raw`\$\$`);
1136
- }
1137
- return res;
1138
- };
1139
-
1140
- // src/react/ai-chat/streaming/utils/getListNode.ts
1141
- var getListNode = (editor, node) => {
1142
- if (node.listStyleType && node.listStart) {
1143
- const previousNode = editor.api.previous({
1144
- at: editor.selection?.focus
1145
- })?.[0];
1146
- if (previousNode?.listStyleType && previousNode?.listStart) {
1147
- return node;
1148
- }
1149
- if (node.listStart === 1) return node;
1150
- return {
1151
- ...node,
1152
- listRestartPolite: node.listStart
1153
- };
1154
- }
1155
- return node;
1156
- };
1157
-
1158
- // src/react/ai-chat/streaming/utils/isSameNode.ts
1159
- import { isDefined, KEYS as KEYS14 } from "platejs";
1160
- var LIST_STYLE_TYPE = "listStyleType";
1161
- var isSameNode = (editor, node1, node2) => {
1162
- if (node1.type !== editor.getType(KEYS14.p) || node2.type !== editor.getType(KEYS14.p))
1163
- return node1.type === node2.type;
1164
- if (isDefined(node1[LIST_STYLE_TYPE]) || isDefined(node2[LIST_STYLE_TYPE])) {
1165
- return node1[LIST_STYLE_TYPE] === node2[LIST_STYLE_TYPE];
1166
- }
1167
- return node1.type === node2.type;
1168
- };
1169
-
1170
- // src/react/ai-chat/streaming/utils/nodesWithProps.ts
1171
- import { ElementApi as ElementApi5 } from "platejs";
1172
- var nodesWithProps = (editor, nodes, options) => nodes.map((node) => {
1173
- if (ElementApi5.isElement(node)) {
1174
- return {
1175
- ...getListNode(editor, node),
1176
- ...options.elementProps,
1177
- children: nodesWithProps(editor, node.children, options)
1178
- };
1179
- }
1180
- return {
1181
- ...options.textProps,
1182
- ...node,
1183
- text: node.text
1184
- };
1185
- });
1186
-
1187
- // src/react/ai-chat/streaming/streamDeserializeMd.ts
1188
- var statMdxTagRegex = /<([A-Za-z][A-Za-z0-9._:-]*)(?:\s[^>]*?)?(?<!\/)>/;
1189
- var streamDeserializeMd = (editor, data, options) => {
1190
- const input = escapeInput(data);
1191
- const value = withoutDeserializeInMdx(editor, input);
1192
- if (Array.isArray(value)) return value;
1193
- let blocks = [];
1194
- blocks = editor.getApi(MarkdownPlugin3).markdown.deserialize(input, {
1195
- ...options,
1196
- preserveEmptyParagraphs: false
1197
- });
1198
- const trimmedData = getChunkTrimmed(data);
1199
- const lastBlock = blocks.at(-1);
1200
- const addNewLine = trimmedData === "\n\n";
1201
- const unshiftNewLine = getChunkTrimmed(data, { direction: "left" }) === "\n\n";
1202
- const isCodeBlockOrTable = lastBlock?.type === "code_block" || lastBlock?.type === "table";
1203
- let result = blocks;
1204
- if (lastBlock && !isCodeBlockOrTable && trimmedData.length > 0 && !addNewLine) {
1205
- const textNode = [
1206
- {
1207
- text: trimmedData
1208
- }
1209
- ];
1210
- const lastChild = lastBlock.children.at(-1);
1211
- if (lastChild && TextApi4.isText(lastChild) && Object.keys(lastChild).length === 1) {
1212
- lastBlock.children.pop();
1213
- const textNode2 = [
1214
- {
1215
- text: lastChild.text + trimmedData
1216
- }
1217
- ];
1218
- lastBlock.children.push(...textNode2);
1219
- } else {
1220
- lastBlock.children.push(...textNode);
1221
- }
1222
- result = [...blocks.slice(0, -1), lastBlock];
1223
- }
1224
- if (addNewLine && !isCodeBlockOrTable) {
1225
- result.push({
1226
- children: [{ text: "" }],
1227
- type: KEYS15.p
1228
- });
1229
- }
1230
- if (unshiftNewLine && !isCodeBlockOrTable) {
1231
- result.unshift({
1232
- children: [{ text: "" }],
1233
- type: KEYS15.p
1234
- });
1235
- }
1236
- return result;
1237
- };
1238
- var withoutDeserializeInMdx = (editor, input) => {
1239
- const mdxName = editor.getOption(AIChatPlugin, "_mdxName");
1240
- if (mdxName) {
1241
- const isMdxEnd = input.includes(`</${mdxName}>`);
1242
- if (isMdxEnd) {
1243
- editor.setOption(AIChatPlugin, "_mdxName", null);
1244
- return false;
1245
- }
1246
- return [
1247
- {
1248
- children: [
1249
- {
1250
- text: input
1251
- }
1252
- ],
1253
- type: getPluginType4(editor, KEYS15.p)
1254
- }
1255
- ];
1256
- }
1257
- const newMdxName = statMdxTagRegex.exec(input)?.[1];
1258
- if (input.startsWith(`<${newMdxName}`)) {
1259
- editor.setOption(AIChatPlugin, "_mdxName", newMdxName ?? null);
1260
- }
1261
- };
1262
-
1263
- // src/react/ai-chat/streaming/streamInsertChunk.ts
1264
- import {
1265
- getPluginType as getPluginType5,
1266
- KEYS as KEYS17,
1267
- NodeApi as NodeApi4,
1268
- PathApi as PathApi2
1269
- } from "platejs";
1270
-
1271
- // src/react/ai-chat/streaming/streamSerializeMd.ts
1272
- import { MarkdownPlugin as MarkdownPlugin4 } from "@platejs/markdown";
1273
- import {
1274
- ElementApi as ElementApi6,
1275
- getPluginKey,
1276
- KEYS as KEYS16,
1277
- TextApi as TextApi5
1278
- } from "platejs";
1279
- var trimEndHeading = (editor, value) => {
1280
- const headingKeys = /* @__PURE__ */ new Set([
1281
- KEYS16.h1,
1282
- KEYS16.h2,
1283
- KEYS16.h3,
1284
- KEYS16.h4,
1285
- KEYS16.h5,
1286
- KEYS16.h6
1287
- ]);
1288
- const lastBlock = value.at(-1);
1289
- if (lastBlock && headingKeys.has(
1290
- getPluginKey(editor, lastBlock.type) ?? lastBlock.type
1291
- ) && ElementApi6.isElement(lastBlock)) {
1292
- const lastTextNode = lastBlock.children.at(-1);
1293
- if (TextApi5.isText(lastTextNode)) {
1294
- const trimmedText = getChunkTrimmed(lastTextNode?.text);
1295
- const newChildren = [
1296
- ...lastBlock.children.slice(0, -1),
1297
- { text: lastTextNode.text.trimEnd() }
1298
- ];
1299
- const newLastBlock = {
1300
- ...lastBlock,
1301
- children: newChildren
1302
- };
1303
- return {
1304
- trimmedText,
1305
- value: [...value.slice(0, -1), newLastBlock]
1306
- };
1307
- }
1308
- }
1309
- return { trimmedText: "", value };
1310
- };
1311
- var streamSerializeMd = (editor, options, chunk) => {
1312
- const { value: optionsValue, ...restOptions } = options;
1313
- const { value } = trimEndHeading(editor, optionsValue ?? editor.children);
1314
- let result = "";
1315
- result = editor.getApi(MarkdownPlugin4).markdown.serialize({
1316
- value,
1317
- ...restOptions
1318
- });
1319
- const trimmedChunk = getChunkTrimmed(chunk);
1320
- if (isCompleteCodeBlock(result) && !chunk.endsWith("```")) {
1321
- result = result.trimEnd().slice(0, -3) + trimmedChunk;
1322
- }
1323
- if (isCompleteMath(result) && !chunk.endsWith("$$")) {
1324
- result = result.trimEnd().slice(0, -3) + trimmedChunk;
1325
- }
1326
- result = result.replace(/&#x20;/g, " ");
1327
- result = result.replace(/&#x200B;/g, " ");
1328
- result = result.replace(/\u200B/g, "");
1329
- if (trimmedChunk !== "\n\n") {
1330
- result = result.trimEnd() + trimmedChunk;
1331
- }
1332
- if (chunk.endsWith("\n\n")) {
1333
- if (result === "\n") {
1334
- result = "";
1335
- } else if (result.endsWith("\n\n")) {
1336
- result = result.slice(0, -1);
1337
- }
1338
- }
1339
- result = result.replace(/\\([\\`*_{}\\[\]()#+\-\\.!~<>|$])/g, "$1");
1340
- return result;
1341
- };
1342
-
1343
- // src/react/ai-chat/streaming/streamInsertChunk.ts
1344
- var getNextPath = (path, length) => {
1345
- let result = path;
1346
- for (let i = 0; i < length; i++) {
1347
- result = PathApi2.next(result);
1348
- }
1349
- return result;
1350
- };
1351
- function streamInsertChunk(editor, chunk, options = {}) {
1352
- const { _blockChunks, _blockPath } = editor.getOptions(AIChatPlugin);
1353
- if (_blockPath === null) {
1354
- const blocks = streamDeserializeMd(editor, chunk);
1355
- const path = getCurrentBlockPath(editor);
1356
- const startBlock = editor.api.node(path)[0];
1357
- const startInEmptyParagraph = NodeApi4.string(startBlock).length === 0 && startBlock.type === getPluginType5(editor, KEYS17.p);
1358
- if (startInEmptyParagraph) {
1359
- editor.tf.removeNodes({ at: path });
1360
- }
1361
- if (blocks.length > 0) {
1362
- editor.tf.insertNodes(nodesWithProps(editor, [blocks[0]], options), {
1363
- at: path,
1364
- nextBlock: !startInEmptyParagraph,
1365
- select: true
1366
- });
1367
- editor.setOption(AIChatPlugin, "_blockPath", getCurrentBlockPath(editor));
1368
- editor.setOption(AIChatPlugin, "_blockChunks", chunk);
1369
- if (blocks.length > 1) {
1370
- const nextBlocks = blocks.slice(1);
1371
- const nextPath = getCurrentBlockPath(editor);
1372
- editor.tf.insertNodes(nodesWithProps(editor, nextBlocks, options), {
1373
- at: nextPath,
1374
- nextBlock: true,
1375
- select: true
1376
- });
1377
- const lastBlock = editor.api.node(
1378
- getNextPath(nextPath, nextBlocks.length)
1379
- );
1380
- editor.setOption(AIChatPlugin, "_blockPath", lastBlock[1]);
1381
- const lastBlockChunks = streamSerializeMd(
1382
- editor,
1383
- {
1384
- value: [lastBlock[0]]
1385
- },
1386
- chunk
1387
- );
1388
- editor.setOption(AIChatPlugin, "_blockChunks", lastBlockChunks);
1389
- }
1390
- }
1391
- } else {
1392
- const tempBlockChunks = _blockChunks + chunk;
1393
- const tempBlocks = streamDeserializeMd(editor, tempBlockChunks);
1394
- if (tempBlocks.length === 0) {
1395
- return console.warn(
1396
- `unsupport md nodes: ${JSON.stringify(tempBlockChunks)}`
1397
- );
1398
- }
1399
- if (tempBlocks.length === 1) {
1400
- const currentBlock = editor.api.node(_blockPath)[0];
1401
- if (isSameNode(editor, currentBlock, tempBlocks[0])) {
1402
- const chunkNodes = streamDeserializeInlineMd(editor, chunk);
1403
- editor.tf.insertNodes(nodesWithProps(editor, chunkNodes, options), {
1404
- at: editor.api.end(_blockPath),
1405
- select: true
1406
- });
1407
- const updatedBlock = editor.api.node(_blockPath);
1408
- const serializedBlock = streamSerializeMd(
1409
- editor,
1410
- {
1411
- value: [updatedBlock[0]]
1412
- },
1413
- tempBlockChunks
1414
- );
1415
- const blockText = NodeApi4.string(tempBlocks[0]);
1416
- if (serializedBlock === tempBlockChunks && blockText === serializedBlock) {
1417
- editor.setOption(AIChatPlugin, "_blockChunks", tempBlockChunks);
1418
- } else {
1419
- editor.tf.replaceNodes(
1420
- nodesWithProps(editor, [tempBlocks[0]], options),
1421
- {
1422
- at: _blockPath,
1423
- select: true
1424
- }
1425
- );
1426
- const serializedBlock2 = streamSerializeMd(
1427
- editor,
1428
- {
1429
- value: [tempBlocks[0]]
1430
- },
1431
- tempBlockChunks
1432
- );
1433
- editor.setOption(
1434
- AIChatPlugin,
1435
- "_blockChunks",
1436
- // one block includes multiple children
1437
- tempBlocks[0].type === getPluginType5(editor, KEYS17.codeBlock) || tempBlocks[0].type === getPluginType5(editor, KEYS17.table) || tempBlocks[0].type === getPluginType5(editor, KEYS17.equation) ? tempBlockChunks : serializedBlock2
1438
- );
1439
- }
1440
- } else {
1441
- const serializedBlock = streamSerializeMd(
1442
- editor,
1443
- {
1444
- value: [tempBlocks[0]]
1445
- },
1446
- tempBlockChunks
1447
- );
1448
- editor.tf.replaceNodes(
1449
- nodesWithProps(editor, [tempBlocks[0]], options),
1450
- {
1451
- at: _blockPath,
1452
- select: true
1453
- }
1454
- );
1455
- editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
1456
- }
1457
- } else {
1458
- editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
1459
- at: _blockPath,
1460
- select: true
1461
- });
1462
- if (tempBlocks.length > 1) {
1463
- const newEndBlockPath = getNextPath(_blockPath, tempBlocks.length - 1);
1464
- editor.tf.insertNodes(
1465
- nodesWithProps(editor, tempBlocks.slice(1), options),
1466
- {
1467
- at: PathApi2.next(_blockPath),
1468
- select: true
1469
- }
1470
- );
1471
- editor.setOption(AIChatPlugin, "_blockPath", newEndBlockPath);
1472
- const endBlock = editor.api.node(newEndBlockPath)[0];
1473
- const serializedBlock = streamSerializeMd(
1474
- editor,
1475
- {
1476
- value: [endBlock]
1477
- },
1478
- tempBlockChunks
1479
- );
1480
- editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
1481
- }
1482
- }
1483
- }
1484
- }
1485
- var getCurrentBlockPath = (editor) => {
1486
- const getAnchorPreviousPath = (editor2) => {
1487
- const anchorNode = editor2.getApi(AIChatPlugin).aiChat.node({ anchor: true });
1488
- if (anchorNode) {
1489
- return PathApi2.previous(anchorNode[1]);
1490
- }
1491
- };
1492
- const getFocusPath = (editor2) => editor2.selection?.focus.path.slice(0, 1);
1493
- const path = getAnchorPreviousPath(editor) ?? getFocusPath(editor) ?? [0];
1494
- const entry = editor.api.node(path);
1495
- if (entry && (entry[0].type === getPluginType5(editor, KEYS17.columnGroup) || entry[0].type === getPluginType5(editor, KEYS17.table))) {
1496
- return editor.api.above()?.[1] ?? path;
1497
- }
1498
- return path;
1499
- };
1500
-
1501
- // src/react/copilot/CopilotPlugin.tsx
1502
- import { serializeMd } from "@platejs/markdown";
1503
- import debounce from "lodash/debounce.js";
1504
- import {
1505
- bindFirst as bindFirst3,
1506
- KEYS as KEYS22,
1507
- NodeApi as NodeApi5
1508
- } from "platejs";
1509
- import { createTPlatePlugin as createTPlatePlugin2 } from "platejs/react";
1510
-
1511
- // src/react/copilot/renderCopilotBelowNodes.tsx
1512
- import React from "react";
1513
- import { KEYS as KEYS18 } from "platejs";
1514
- import { getEditorPlugin as getEditorPlugin4 } from "platejs/react";
1515
- var renderCopilotBelowNodes = ({
1516
- editor
1517
- }) => {
1518
- const copilot = getEditorPlugin4(editor, {
1519
- key: KEYS18.copilot
1520
- });
1521
- const { renderGhostText: GhostText } = copilot.getOptions();
1522
- if (!GhostText) return;
1523
- return ({ children }) => /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement(GhostText, null));
1524
- };
1525
-
1526
- // src/react/copilot/transforms/acceptCopilot.ts
1527
- import { deserializeInlineMd } from "@platejs/markdown";
1528
- import { KEYS as KEYS19 } from "platejs";
1529
- var acceptCopilot = (editor) => {
1530
- const { suggestionText } = editor.getOptions({
1531
- key: KEYS19.copilot
1532
- });
1533
- if (!suggestionText?.length) return false;
1534
- editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
1535
- };
1536
-
1537
- // src/react/copilot/transforms/acceptCopilotNextWord.ts
1538
- import { deserializeInlineMd as deserializeInlineMd2 } from "@platejs/markdown";
1539
- import { KEYS as KEYS21 } from "platejs";
1540
- import { getEditorPlugin as getEditorPlugin6 } from "platejs/react";
1541
-
1542
- // src/react/copilot/utils/callCompletionApi.ts
1543
- var getOriginalFetch = () => fetch;
1544
- async function callCompletionApi({
1545
- api = "/api/completion",
1546
- body,
1547
- credentials,
1548
- fetch: fetch2 = getOriginalFetch(),
1549
- headers,
1550
- prompt,
1551
- setAbortController = () => {
1552
- },
1553
- setCompletion = () => {
1554
- },
1555
- setError = () => {
1556
- },
1557
- setLoading = () => {
1558
- },
1559
- onError,
1560
- onFinish,
1561
- onResponse
1562
- }) {
1563
- try {
1564
- setLoading(true);
1565
- setError(null);
1566
- const abortController = new AbortController();
1567
- setAbortController(abortController);
1568
- setCompletion("");
1569
- const res = await fetch2(api, {
1570
- body: JSON.stringify({
1571
- prompt,
1572
- ...body
1573
- }),
1574
- credentials,
1575
- headers: {
1576
- "Content-Type": "application/json",
1577
- ...headers
1578
- },
1579
- method: "POST",
1580
- signal: abortController.signal
1581
- }).catch((error) => {
1582
- throw error;
1583
- });
1584
- if (onResponse) {
1585
- await onResponse(res);
1586
- }
1587
- if (!res.ok) {
1588
- throw new Error(
1589
- await res.text() || "Failed to fetch the chat response."
1590
- );
1591
- }
1592
- if (!res.body) {
1593
- throw new Error("The response body is empty.");
1594
- }
1595
- const { text } = await res.json();
1596
- if (!text) {
1597
- throw new Error("The response does not contain a text field.");
1598
- }
1599
- setCompletion(text);
1600
- if (onFinish) {
1601
- onFinish(prompt, text);
1602
- }
1603
- setAbortController(null);
1604
- return text;
1605
- } catch (error) {
1606
- if (error.name === "AbortError") {
1607
- setAbortController(null);
1608
- return null;
1609
- }
1610
- if (error instanceof Error && onError) {
1611
- onError(error);
1612
- }
1613
- setError(error);
1614
- } finally {
1615
- setLoading(false);
1616
- }
1617
- }
1618
-
1619
- // src/react/copilot/utils/getNextWord.ts
1620
- var nonSpaceRegex = /^\s*(\S)/;
1621
- var cjkCharRegex = /[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/;
1622
- var cjkMatchRegex = /^(\s*)([\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF])([\u3000-\u303F\uFF00-\uFFEF])?/;
1623
- var nonCjkMatchRegex = /^(\s*\S+?)(?=[\s\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]|$)/;
1624
- var getNextWord = ({ text }) => {
1625
- if (!text) return { firstWord: "", remainingText: "" };
1626
- const nonSpaceMatch = nonSpaceRegex.exec(text);
1627
- if (!nonSpaceMatch) return { firstWord: "", remainingText: "" };
1628
- const firstNonSpaceChar = nonSpaceMatch[1];
1629
- const isCJKChar = cjkCharRegex.test(firstNonSpaceChar);
1630
- let firstWord;
1631
- let remainingText;
1632
- if (isCJKChar) {
1633
- const match = cjkMatchRegex.exec(text);
1634
- if (match) {
1635
- const [, spaces = "", char = "", punctuation = ""] = match;
1636
- firstWord = spaces + char + punctuation;
1637
- remainingText = text.slice(firstWord.length);
1638
- } else {
1639
- firstWord = "";
1640
- remainingText = text;
1641
- }
1642
- } else {
1643
- const match = nonCjkMatchRegex.exec(text);
1644
- if (match) {
1645
- firstWord = match[0];
1646
- remainingText = text.slice(firstWord.length);
1647
- } else {
1648
- firstWord = text;
1649
- remainingText = "";
1650
- }
1651
- }
1652
- return { firstWord, remainingText };
1653
- };
1654
-
1655
- // src/react/copilot/utils/triggerCopilotSuggestion.ts
1656
- import { KEYS as KEYS20 } from "platejs";
1657
- import { getEditorPlugin as getEditorPlugin5 } from "platejs/react";
1658
- var triggerCopilotSuggestion = async (editor) => {
1659
- const { api, getOptions, setOption } = getEditorPlugin5(
1660
- editor,
1661
- {
1662
- key: KEYS20.copilot
1663
- }
1664
- );
1665
- const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
1666
- if (isLoading || editor.getOptions({ key: KEYS20.aiChat }).chat?.isLoading) {
1667
- return false;
1668
- }
1669
- if (!triggerQuery({ editor })) return false;
1670
- const prompt = getPrompt({ editor });
1671
- if (prompt.length === 0) return false;
1672
- api.copilot.stop();
1673
- await callCompletionApi({
1674
- prompt,
1675
- onFinish: (_, completion) => {
1676
- api.copilot.setBlockSuggestion({ text: completion });
1677
- },
1678
- ...completeOptions,
1679
- setAbortController: (controller) => setOption("abortController", controller),
1680
- setCompletion: (completion) => setOption("completion", completion),
1681
- setError: (error) => setOption("error", error),
1682
- setLoading: (loading) => setOption("isLoading", loading),
1683
- onError: (error) => {
1684
- setOption("error", error);
1685
- completeOptions?.onError?.(error);
1686
- }
1687
- });
1688
- };
1689
-
1690
- // src/react/copilot/utils/withoutAbort.ts
1691
- var withoutAbort = (editor, fn) => {
1692
- editor.setOption(CopilotPlugin, "shouldAbort", false);
1693
- fn();
1694
- editor.setOption(CopilotPlugin, "shouldAbort", true);
1695
- };
1696
-
1697
- // src/react/copilot/transforms/acceptCopilotNextWord.ts
1698
- var acceptCopilotNextWord = (editor) => {
1699
- const { api, getOptions } = getEditorPlugin6(editor, {
1700
- key: KEYS21.copilot
1701
- });
1702
- const { getNextWord: getNextWord2, suggestionText } = getOptions();
1703
- if (!suggestionText?.length) {
1704
- return false;
1705
- }
1706
- const { firstWord, remainingText } = getNextWord2({ text: suggestionText });
1707
- api.copilot.setBlockSuggestion({
1708
- text: remainingText
1709
- });
1710
- withoutAbort(editor, () => {
1711
- editor.tf.insertFragment(deserializeInlineMd2(editor, firstWord));
1712
- });
1713
- };
1714
-
1715
- // src/react/copilot/withCopilot.ts
1716
- import { serializeInlineMd } from "@platejs/markdown";
1717
- import {
1718
- RangeApi as RangeApi2
1719
- } from "platejs";
1720
- var getPatchString = (editor, operations) => {
1721
- let string = "";
1722
- for (const operation of operations) {
1723
- if (operation.type === "insert_node") {
1724
- const node = operation.node;
1725
- const text = serializeInlineMd(editor, { value: [node] });
1726
- string += text;
1727
- } else if (operation.type === "insert_text") {
1728
- string += operation.text;
1729
- }
1730
- }
1731
- return string;
1732
- };
1733
- var withCopilot = ({
1734
- api,
1735
- editor,
1736
- getOptions,
1737
- setOption,
1738
- tf: { apply, insertText, redo, setSelection, undo, writeHistory }
1739
- }) => {
1740
- let prevSelection = null;
1741
- return {
1742
- transforms: {
1743
- apply(operation) {
1744
- const { shouldAbort } = getOptions();
1745
- if (shouldAbort) {
1746
- api.copilot.reject();
1747
- }
1748
- apply(operation);
1749
- },
1750
- insertText(text, options) {
1751
- const suggestionText = getOptions().suggestionText;
1752
- if (suggestionText?.startsWith(text)) {
1753
- withoutAbort(editor, () => {
1754
- editor.tf.withoutMerging(() => {
1755
- const newText = suggestionText?.slice(text.length);
1756
- setOption("suggestionText", newText);
1757
- insertText(text);
1758
- });
1759
- });
1760
- return;
1761
- }
1762
- insertText(text, options);
1763
- },
1764
- redo() {
1765
- if (!getOptions().suggestionText) return redo();
1766
- const topRedo = editor.history.redos.at(-1);
1767
- const prevSuggestion = getOptions().suggestionText;
1768
- if (topRedo && topRedo.shouldAbort === false && prevSuggestion) {
1769
- withoutAbort(editor, () => {
1770
- const shouldRemoveText = getPatchString(editor, topRedo.operations);
1771
- const newText = prevSuggestion.slice(shouldRemoveText.length);
1772
- setOption("suggestionText", newText);
1773
- redo();
1774
- });
1775
- return;
1776
- }
1777
- return redo();
1778
- },
1779
- setSelection(props) {
1780
- setSelection(props);
1781
- if (editor.selection && (!prevSelection || !RangeApi2.equals(prevSelection, editor.selection)) && getOptions().autoTriggerQuery({ editor }) && editor.api.isFocused()) {
1782
- void api.copilot.triggerSuggestion();
1783
- }
1784
- prevSelection = editor.selection;
1785
- },
1786
- undo() {
1787
- if (!getOptions().suggestionText) return undo();
1788
- const lastUndos = editor.history.undos.at(-1);
1789
- const oldText = getOptions().suggestionText;
1790
- if (lastUndos && lastUndos.shouldAbort === false && oldText) {
1791
- withoutAbort(editor, () => {
1792
- const shouldInsertText = getPatchString(
1793
- editor,
1794
- lastUndos.operations
1795
- );
1796
- const newText = shouldInsertText + oldText;
1797
- setOption("suggestionText", newText);
1798
- undo();
1799
- });
1800
- return;
1801
- }
1802
- return undo();
1803
- },
1804
- writeHistory(stacks, batch) {
1805
- if (!getOptions().isLoading) {
1806
- batch.shouldAbort = getOptions().shouldAbort;
1807
- }
1808
- return writeHistory(stacks, batch);
1809
- }
1810
- }
1811
- };
1812
- };
1813
-
1814
- // src/react/copilot/CopilotPlugin.tsx
1815
- var CopilotPlugin = createTPlatePlugin2({
1816
- key: KEYS22.copilot,
1817
- handlers: {
1818
- onBlur: ({ api }) => {
1819
- api.copilot.reject();
1820
- },
1821
- onMouseDown: ({ api }) => {
1822
- api.copilot.reject();
1823
- }
1824
- },
1825
- options: {
1826
- abortController: null,
1827
- completeOptions: {},
1828
- completion: "",
1829
- debounceDelay: 0,
1830
- error: null,
1831
- getNextWord,
1832
- isLoading: false,
1833
- renderGhostText: null,
1834
- shouldAbort: true,
1835
- suggestionNodeId: null,
1836
- suggestionText: null,
1837
- autoTriggerQuery: ({ editor }) => {
1838
- if (editor.getOptions({ key: KEYS22.copilot }).suggestionText) {
1839
- return false;
1840
- }
1841
- const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
1842
- if (isEmpty) return false;
1843
- const blockAbove = editor.api.block();
1844
- if (!blockAbove) return false;
1845
- const blockString = NodeApi5.string(blockAbove[0]);
1846
- return blockString.at(-1) === " ";
1847
- },
1848
- getPrompt: ({ editor }) => {
1849
- const contextEntry = editor.api.block({ highest: true });
1850
- if (!contextEntry) return "";
1851
- return serializeMd(editor, {
1852
- value: [contextEntry[0]]
1853
- });
1854
- },
1855
- triggerQuery: ({ editor }) => {
1856
- if (editor.api.isExpanded()) return false;
1857
- const isEnd = editor.api.isAt({ end: true });
1858
- if (!isEnd) return false;
1859
- return true;
1860
- }
1861
- }
1862
- }).overrideEditor(withCopilot).extendSelectors(({ getOptions }) => ({
1863
- isSuggested: (id) => getOptions().suggestionNodeId === id
1864
- })).extendTransforms(({ editor }) => ({
1865
- accept: bindFirst3(acceptCopilot, editor),
1866
- acceptNextWord: bindFirst3(acceptCopilotNextWord, editor)
1867
- })).extendApi(
1868
- ({ api, editor, getOptions, setOption, setOptions }) => {
1869
- const debounceDelay = getOptions().debounceDelay;
1870
- let triggerSuggestion = bindFirst3(triggerCopilotSuggestion, editor);
1871
- if (debounceDelay) {
1872
- triggerSuggestion = debounce(
1873
- bindFirst3(triggerCopilotSuggestion, editor),
1874
- debounceDelay
1875
- );
1876
- }
1877
- return {
1878
- triggerSuggestion,
1879
- setBlockSuggestion: ({ id = getOptions().suggestionNodeId, text }) => {
1880
- if (!id) {
1881
- id = editor.api.block()[0].id;
1882
- }
1883
- setOptions({
1884
- suggestionNodeId: id,
1885
- suggestionText: text
1886
- });
1887
- },
1888
- stop: () => {
1889
- const { abortController } = getOptions();
1890
- api.copilot.triggerSuggestion?.cancel();
1891
- if (abortController) {
1892
- abortController.abort();
1893
- setOption("abortController", null);
1894
- }
1895
- }
1896
- };
1897
- }
1898
- ).extendApi(({ api, getOptions, setOptions }) => ({
1899
- reject: () => {
1900
- if (!getOptions().suggestionText?.length) return false;
1901
- api.copilot.stop();
1902
- setOptions({
1903
- completion: null,
1904
- suggestionNodeId: null,
1905
- suggestionText: null
1906
- });
1907
- }
1908
- })).extend({
1909
- render: {
1910
- belowNodes: renderCopilotBelowNodes
1911
- },
1912
- shortcuts: {
1913
- accept: {
1914
- keys: "tab"
1915
- },
1916
- reject: {
1917
- keys: "escape"
1918
- }
1919
- }
1920
- });
1921
- export {
1922
- AIChatPlugin,
1923
- AIPlugin,
1924
- CopilotPlugin,
1925
- acceptAIChat,
1926
- acceptAISuggestions,
1927
- acceptCopilot,
1928
- acceptCopilotNextWord,
1929
- aiCommentToRange,
1930
- applyAISuggestions,
1931
- callCompletionApi,
1932
- createFormattedBlocks,
1933
- escapeInput,
1934
- findTextRangeInBlock,
1935
- getChunkTrimmed,
1936
- getCurrentBlockPath,
1937
- getLastAssistantMessage,
1938
- getListNode,
1939
- getNextWord,
1940
- insertBelowAIChat,
1941
- insertBelowGenerate,
1942
- isCompleteCodeBlock,
1943
- isCompleteMath,
1944
- isSameNode,
1945
- nodesWithProps,
1946
- rejectAISuggestions,
1947
- removeAnchorAIChat,
1948
- renderCopilotBelowNodes,
1949
- replaceSelectionAIChat,
1950
- resetAIChat,
1951
- streamDeserializeInlineMd,
1952
- streamDeserializeMd,
1953
- streamInsertChunk,
1954
- streamSerializeMd,
1955
- submitAIChat,
1956
- triggerCopilotSuggestion,
1957
- useAIChatEditor,
1958
- useChatChunk,
1959
- useEditorChat,
1960
- useLastAssistantMessage,
1961
- withAIChat,
1962
- withCopilot,
1963
- withoutAbort
1964
- };
1965
- //# sourceMappingURL=index.mjs.map