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