@platejs/ai 51.1.2 → 52.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1988 +1,1351 @@
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");
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 { c } from "react/compiler-runtime";
12
+ import React, { useEffect, useMemo, useRef } from "react";
13
+ import debounce from "lodash/debounce.js";
80
14
 
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
- };
151
-
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
- }));
15
+ //#region src/react/ai/AIPlugin.ts
16
+ const AIPlugin = toPlatePlugin(BaseAIPlugin);
162
17
 
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
- };
197
-
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");
18
+ //#endregion
19
+ //#region src/react/ai/utils/findTextRangeInBlock.tsx
208
20
  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;
21
+ if (len <= 2) return 0;
22
+ if (len <= 5) return 1;
23
+ if (len <= 10) return 2;
24
+ if (len <= 20) return 3;
25
+ return 5;
214
26
  }
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
- };
27
+ /** Find text within a block, supporting fuzzy matching. */
28
+ function findTextRangeInBlock({ block, findText }) {
29
+ const [blockNode, blockPath] = block;
30
+ const textSegments = [];
31
+ let fullText = "";
32
+ for (const [textNode, textPath] of NodeApi.texts(blockNode)) {
33
+ const startOffset = fullText.length;
34
+ const absolutePath = [...blockPath, ...textPath];
35
+ textSegments.push({
36
+ offset: startOffset,
37
+ path: absolutePath,
38
+ text: textNode.text
39
+ });
40
+ fullText += textNode.text;
41
+ }
42
+ if (!fullText) return null;
43
+ let matchStart = fullText.indexOf(findText);
44
+ let matchEnd = matchStart >= 0 ? matchStart + findText.length : -1;
45
+ if (matchStart === -1) {
46
+ const maxDist = maxAllowedDistance(findText.length);
47
+ let bestMatch = {
48
+ distance: Number.POSITIVE_INFINITY,
49
+ end: -1,
50
+ start: -1
51
+ };
52
+ for (let i = 0; i <= fullText.length - findText.length; i++) for (let lenOffset = -maxDist; lenOffset <= maxDist; lenOffset++) {
53
+ const len = findText.length + lenOffset;
54
+ if (len <= 0 || i + len > fullText.length) continue;
55
+ const dist = distance(fullText.slice(i, i + len), findText);
56
+ if (dist <= maxDist && dist < bestMatch.distance) bestMatch = {
57
+ distance: dist,
58
+ end: i + len,
59
+ start: i
60
+ };
61
+ }
62
+ if (bestMatch.start !== -1) {
63
+ matchStart = bestMatch.start;
64
+ matchEnd = bestMatch.end;
65
+ }
66
+ }
67
+ if (matchStart === -1) for (let prefixLen = findText.length - 1; prefixLen > 0; prefixLen--) {
68
+ const prefix = findText.slice(0, Math.max(0, prefixLen));
69
+ const idx = fullText.indexOf(prefix);
70
+ if (idx !== -1) {
71
+ matchStart = idx;
72
+ matchEnd = idx + prefixLen;
73
+ break;
74
+ }
75
+ }
76
+ if (matchStart === -1) return null;
77
+ const findPoint = (charOffset, isEnd = false) => {
78
+ if (!isEnd) {
79
+ for (const segment of textSegments) if (charOffset === segment.offset) return {
80
+ offset: 0,
81
+ path: segment.path
82
+ };
83
+ }
84
+ for (const segment of textSegments) {
85
+ const segmentEnd = segment.offset + segment.text.length;
86
+ if (charOffset >= segment.offset && charOffset <= segmentEnd) return {
87
+ offset: charOffset - segment.offset,
88
+ path: segment.path
89
+ };
90
+ }
91
+ const lastSegment = textSegments.at(-1);
92
+ if (!lastSegment) return {
93
+ offset: 0,
94
+ path: blockPath
95
+ };
96
+ return {
97
+ offset: lastSegment.text.length,
98
+ path: lastSegment.path
99
+ };
100
+ };
101
+ return {
102
+ anchor: findPoint(matchStart, false),
103
+ focus: findPoint(matchEnd, true)
104
+ };
302
105
  }
303
106
 
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
- }
107
+ //#endregion
108
+ //#region src/react/ai/utils/aiCommentToRange.ts
109
+ const aiCommentToRange = (editor, aiComment) => {
110
+ const { blockId, content } = aiComment;
111
+ const contentNodes = deserializeMd(editor, content);
112
+ let firstBlock;
113
+ const ranges = [];
114
+ contentNodes.forEach((node, index) => {
115
+ let currentBlock;
116
+ if (index === 0) {
117
+ firstBlock = editor.api.node({
118
+ id: blockId,
119
+ at: []
120
+ });
121
+ currentBlock = firstBlock;
122
+ } else {
123
+ if (!firstBlock) return;
124
+ const [_, firstBlockPath] = firstBlock;
125
+ const blockPath = [firstBlockPath[0] + index];
126
+ currentBlock = editor.api.node(blockPath);
127
+ }
128
+ if (!currentBlock) return;
129
+ const range = findTextRangeInBlock({
130
+ block: currentBlock,
131
+ findText: NodeApi.string(node)
132
+ });
133
+ if (!range) return;
134
+ ranges.push(range);
135
+ });
136
+ if (ranges.length === 0) return;
137
+ if (ranges.length > 1) {
138
+ const startRange = ranges[0];
139
+ const endRange = ranges.at(-1);
140
+ return {
141
+ anchor: startRange.anchor,
142
+ focus: endRange.focus
143
+ };
144
+ }
145
+ if (ranges.length === 1) return ranges[0];
341
146
  };
342
147
 
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
- });
148
+ //#endregion
149
+ //#region src/react/ai-chat/utils/acceptAISuggestions.ts
150
+ const acceptAISuggestions = (editor) => {
151
+ editor.getApi(SuggestionPlugin).suggestion.nodes({ transient: true }).forEach(([suggestionNode]) => {
152
+ const suggestionData = editor.getApi(SuggestionPlugin).suggestion.suggestionData(suggestionNode);
153
+ if (!suggestionData) return;
154
+ acceptSuggestion(editor, {
155
+ createdAt: new Date(suggestionData.createdAt),
156
+ keyId: getSuggestionKey(suggestionData.id),
157
+ suggestionId: suggestionData.id,
158
+ type: suggestionData.type,
159
+ userId: suggestionData.userId
160
+ });
161
+ });
162
+ editor.tf.unsetNodes([getTransientSuggestionKey()], {
163
+ at: [],
164
+ mode: "all",
165
+ match: (n) => !!n[getTransientSuggestionKey()]
166
+ });
376
167
  };
377
168
 
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
- }
169
+ //#endregion
170
+ //#region src/react/ai-chat/transforms/acceptAIChat.ts
171
+ const acceptAIChat = (editor) => {
172
+ const mode = editor.getOption(AIChatPlugin, "mode");
173
+ if (mode === "insert") {
174
+ const { tf } = getEditorPlugin(editor, AIPlugin);
175
+ const api = editor.getApi({ key: KEYS.ai });
176
+ const lastAINodePath = api.aiChat.node({
177
+ at: [],
178
+ reverse: true
179
+ })[1];
180
+ withAIBatch(editor, () => {
181
+ tf.ai.removeMarks();
182
+ editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
183
+ });
184
+ api.aiChat.hide();
185
+ editor.tf.focus();
186
+ const focusPoint = editor.api.end(lastAINodePath);
187
+ editor.tf.setSelection({
188
+ anchor: focusPoint,
189
+ focus: focusPoint
190
+ });
191
+ }
192
+ if (mode === "chat") {
193
+ withAIBatch(editor, () => {
194
+ acceptAISuggestions(editor);
195
+ });
196
+ editor.getApi(AIChatPlugin).aiChat.hide();
197
+ }
403
198
  };
404
199
 
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
- }
200
+ //#endregion
201
+ //#region src/react/ai-chat/utils/applyAISuggestions.ts
202
+ const applyAISuggestions = (editor, content) => {
203
+ /** Conflict with block selection */
204
+ editor.getApi({ key: KEYS.cursorOverlay })?.cursorOverlay?.removeCursor("selection");
205
+ const isBlockSelecting = editor.getOption(BlockSelectionPlugin, "isSelectingSome");
206
+ const { chatNodes } = editor.getOptions(AIChatPlugin);
207
+ if (isBlockSelecting || editor.api.blocks({ mode: "highest" }).length > 1) {
208
+ const setReplaceIds = (ids) => {
209
+ editor.setOption(AIChatPlugin, "_replaceIds", ids);
210
+ };
211
+ if (editor.getOption(AIChatPlugin, "_replaceIds").length === 0) setReplaceIds(chatNodes.map((node) => node.id));
212
+ const diffNodes = getDiffNodes(editor, content);
213
+ const replaceNodes = Array.from(editor.api.nodes({
214
+ at: [],
215
+ match: (n) => ElementApi.isElement(n) && editor.getOption(AIChatPlugin, "_replaceIds").includes(n.id)
216
+ }));
217
+ replaceNodes.forEach(([node, path], index) => {
218
+ const replaceNode = node;
219
+ const diffNode = diffNodes[index];
220
+ const isSameString = SkipSuggestionDeletes(editor, replaceNode) === SkipSuggestionDeletes(editor, diffNode);
221
+ const isSameSuggestion = replaceNode.suggestion?.type === diffNode.suggestion?.type;
222
+ if (index === replaceNodes.length - 1 && diffNodes.length > replaceNodes.length) editor.tf.replaceNodes(diffNodes.slice(index), { at: path });
223
+ if (isSameString && isSameSuggestion && node.id === diffNode.id) return;
224
+ editor.tf.replaceNodes(diffNode, { at: path });
225
+ });
226
+ editor.getApi(BlockSelectionPlugin).blockSelection.set(diffNodes.map((node) => node.id));
227
+ setReplaceIds(diffNodes.map((node) => node.id));
228
+ } else {
229
+ const diffNodes = getDiffNodes(editor, content);
230
+ editor.tf.insertFragment(diffNodes);
231
+ const nodes = Array.from(editor.api.nodes({
232
+ at: [],
233
+ mode: "lowest",
234
+ match: (n) => TextApi.isText(n) && !!n[getTransientSuggestionKey()]
235
+ }));
236
+ const range = editor.api.nodesRange(nodes);
237
+ editor.tf.setSelection(range);
238
+ return;
239
+ }
469
240
  };
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
- };
241
+ const withProps = (diffNodes, chatNodes) => diffNodes.map((node, index) => {
242
+ if (!ElementApi.isElement(node)) return node;
243
+ const originalNode = chatNodes?.[index];
244
+ return {
245
+ ...node,
246
+ ...originalNode ?? { id: nanoid() },
247
+ children: node.children
248
+ };
478
249
  });
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
- };
250
+ const withTransient = (diffNodes) => diffNodes.map((node) => {
251
+ if (TextApi.isText(node)) return {
252
+ ...node,
253
+ [getTransientSuggestionKey()]: true
254
+ };
255
+ return {
256
+ ...node,
257
+ children: withTransient(node.children),
258
+ [getTransientSuggestionKey()]: true
259
+ };
491
260
  });
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;
261
+ const withoutSuggestionAndComments = (nodes) => nodes.map((node) => {
262
+ if (TextApi.isText(node)) {
263
+ if (node[KEYS.suggestion] || node[KEYS.comment]) return { text: node.text };
264
+ return node;
265
+ }
266
+ if (ElementApi.isElement(node)) {
267
+ if (node[KEYS.suggestion]) {
268
+ const nodeWithoutSuggestion = {};
269
+ Object.keys(node).forEach((key) => {
270
+ if (key !== KEYS.suggestion && !key.startsWith(KEYS.suggestion)) nodeWithoutSuggestion[key] = node[key];
271
+ });
272
+ return {
273
+ ...nodeWithoutSuggestion,
274
+ children: withoutSuggestionAndComments(node.children)
275
+ };
276
+ }
277
+ return {
278
+ ...node,
279
+ children: withoutSuggestionAndComments(node.children)
280
+ };
281
+ }
282
+ return node;
520
283
  });
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;
284
+ const getDiffNodes = (editor, aiContent) => {
285
+ /** Original document nodes */
286
+ const chatNodes = withoutSuggestionAndComments(editor.getOption(AIChatPlugin, "chatNodes"));
287
+ return withTransient(diffToSuggestions(editor, chatNodes, withProps(deserializeMd(editor, aiContent), chatNodes), { ignoreProps: ["id", "listStart"] }));
532
288
  };
533
289
 
534
- // src/react/ai-chat/utils/getLastAssistantMessage.ts
535
- var import_react5 = require("platejs/react");
290
+ //#endregion
291
+ //#region src/react/ai-chat/utils/getLastAssistantMessage.ts
536
292
  function getLastAssistantMessage(editor) {
537
- const messages = editor.getOptions(AIChatPlugin).chat.messages;
538
- return messages?.findLast((message) => message.role === "assistant");
293
+ return editor.getOptions(AIChatPlugin).chat.messages?.findLast((message) => message.role === "assistant");
539
294
  }
540
295
  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");
296
+ const $ = c(2);
297
+ const toolName = usePluginOption(AIChatPlugin, "toolName");
298
+ const chat = usePluginOption(AIChatPlugin, "chat");
299
+ if (toolName === "comment") return;
300
+ let t0;
301
+ if ($[0] !== chat.messages) {
302
+ t0 = chat.messages?.findLast(_temp);
303
+ $[0] = chat.messages;
304
+ $[1] = t0;
305
+ } else t0 = $[1];
306
+ return t0;
307
+ }
308
+ function _temp(message) {
309
+ return message.role === "assistant";
545
310
  }
546
311
 
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
- });
312
+ //#endregion
313
+ //#region src/react/ai-chat/utils/rejectAISuggestions.ts
314
+ const rejectAISuggestions = (editor) => {
315
+ editor.getApi(SuggestionPlugin).suggestion.nodes({ transient: true }).forEach(([suggestionNode]) => {
316
+ const suggestionData = editor.getApi(SuggestionPlugin).suggestion.suggestionData(suggestionNode);
317
+ if (!suggestionData) return;
318
+ rejectSuggestion(editor, {
319
+ createdAt: new Date(suggestionData.createdAt),
320
+ keyId: getSuggestionKey(suggestionData.id),
321
+ suggestionId: suggestionData.id,
322
+ type: suggestionData.type,
323
+ userId: suggestionData.userId
324
+ });
325
+ });
326
+ editor.tf.unsetNodes([getTransientSuggestionKey()], {
327
+ at: [],
328
+ mode: "all",
329
+ match: (n) => !!n[getTransientSuggestionKey()]
330
+ });
571
331
  };
572
332
 
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
- }
333
+ //#endregion
334
+ //#region src/react/ai-chat/utils/resetAIChat.ts
335
+ const resetAIChat = (editor, { undo = true } = {}) => {
336
+ const { api, getOptions, setOptions } = getEditorPlugin(editor, { key: KEYS.aiChat });
337
+ api.aiChat.stop();
338
+ const chat = getOptions().chat;
339
+ if (chat.messages && chat.messages.length > 0) chat.setMessages?.([]);
340
+ setOptions({
341
+ _replaceIds: [],
342
+ chatNodes: [],
343
+ mode: "insert",
344
+ toolName: null
345
+ });
346
+ if (undo) editor.getTransforms(AIPlugin).ai.undo();
597
347
  };
598
348
 
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
- );
349
+ //#endregion
350
+ //#region src/react/ai-chat/utils/submitAIChat.ts
351
+ const submitAIChat = (editor, input, { mode, options, prompt, toolName: toolNameProps } = {}) => {
352
+ const { getOptions, setOption } = getEditorPlugin(editor, { key: KEYS.aiChat });
353
+ const { chat, toolName: toolNameOption } = getOptions();
354
+ const toolName = toolNameProps ?? toolNameOption ?? null;
355
+ if (!prompt && input?.length === 0) return;
356
+ if (!prompt) prompt = input;
357
+ if (!mode) mode = isSelecting(editor) ? "chat" : "insert";
358
+ if (mode === "insert") editor.getTransforms(AIPlugin).ai.undo();
359
+ setOption("mode", mode);
360
+ setOption("toolName", toolName);
361
+ const blocks = editor.getApi(BlockSelectionPlugin).blockSelection.getNodes();
362
+ const blocksRange = editor.api.nodesRange(blocks);
363
+ const promptText = getEditorPrompt(editor, { prompt });
364
+ const selection = blocks.length > 0 ? blocksRange : editor.selection;
365
+ let chatNodes;
366
+ if (blocks.length > 0) chatNodes = blocks.map((block) => block[0]);
367
+ else {
368
+ const selectionBlocks = editor.api.blocks({ mode: "highest" });
369
+ if (selectionBlocks.length > 1) chatNodes = selectionBlocks.map((block) => block[0]);
370
+ else chatNodes = editor.api.fragment();
371
+ }
372
+ setOption("chatNodes", chatNodes);
373
+ setOption("chatSelection", blocks.length > 0 ? null : editor.selection);
374
+ const ctx = {
375
+ children: editor.children,
376
+ selection: selection ?? null,
377
+ toolName
378
+ };
379
+ chat.sendMessage?.({ text: promptText }, {
380
+ body: { ctx },
381
+ ...options
382
+ });
667
383
  };
668
384
 
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
- });
385
+ //#endregion
386
+ //#region src/react/ai-chat/transforms/replaceSelectionAIChat.ts
387
+ const createFormattedBlocks = ({ blocks, format, sourceBlock }) => {
388
+ if (format === "none") return cloneDeep(blocks);
389
+ const [sourceNode] = sourceBlock;
390
+ const firstTextEntry = NodeApi.firstText(sourceNode);
391
+ if (!firstTextEntry) return null;
392
+ const blockProps = NodeApi.extractProps(sourceNode);
393
+ const textProps = NodeApi.extractProps(firstTextEntry[0]);
394
+ const applyTextFormatting = (node) => {
395
+ if (TextApi.isText(node)) return {
396
+ ...textProps,
397
+ ...node
398
+ };
399
+ if (node.children) return {
400
+ ...node,
401
+ children: node.children.map(applyTextFormatting)
402
+ };
403
+ return node;
404
+ };
405
+ return blocks.map((block, index) => {
406
+ if (format === "single" && index > 0) return block;
407
+ return applyTextFormatting({
408
+ ...block,
409
+ ...blockProps
410
+ });
411
+ });
705
412
  };
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();
413
+ const replaceSelectionAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
414
+ if (!sourceEditor || sourceEditor.api.isEmpty()) return;
415
+ const isBlockSelecting = editor.getOption(BlockSelectionPlugin, "isSelectingSome");
416
+ editor.getApi({ key: KEYS.ai }).aiChat.hide();
417
+ if (!isBlockSelecting) {
418
+ const firstBlock = editor.api.node({
419
+ block: true,
420
+ mode: "lowest"
421
+ });
422
+ if (firstBlock && editor.api.isSelected(firstBlock[1], { contains: true }) && format !== "none") {
423
+ const formattedBlocks$1 = createFormattedBlocks({
424
+ blocks: cloneDeep(sourceEditor.children),
425
+ format,
426
+ sourceBlock: firstBlock
427
+ });
428
+ if (!formattedBlocks$1) return;
429
+ /** When user selection is cover the whole code block */
430
+ if (firstBlock[0].type === KEYS.codeLine && sourceEditor.children[0].type === KEYS.codeBlock && sourceEditor.children.length === 1) editor.tf.insertFragment(formattedBlocks$1[0].children);
431
+ else editor.tf.insertFragment(formattedBlocks$1);
432
+ editor.tf.focus();
433
+ return;
434
+ }
435
+ editor.tf.insertFragment(sourceEditor.children);
436
+ editor.tf.focus();
437
+ return;
438
+ }
439
+ const selectedBlocks = editor.getApi(BlockSelectionPlugin).blockSelection.getNodes();
440
+ if (selectedBlocks.length === 0) return;
441
+ if (format === "none" || format === "single" && selectedBlocks.length > 1) {
442
+ editor.tf.withoutNormalizing(() => {
443
+ removeBlockSelectionNodes(editor);
444
+ editor.tf.withNewBatch(() => {
445
+ editor.getTransforms(BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(cloneDeep(sourceEditor.children), { at: selectedBlocks[0][1] });
446
+ });
447
+ });
448
+ editor.getApi(BlockSelectionPlugin).blockSelection.focus();
449
+ return;
450
+ }
451
+ const [, firstBlockPath] = selectedBlocks[0];
452
+ const formattedBlocks = createFormattedBlocks({
453
+ blocks: cloneDeep(sourceEditor.children),
454
+ format,
455
+ sourceBlock: selectedBlocks[0]
456
+ });
457
+ if (!formattedBlocks) return;
458
+ editor.tf.withoutNormalizing(() => {
459
+ removeBlockSelectionNodes(editor);
460
+ editor.tf.withNewBatch(() => {
461
+ editor.getTransforms(BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(formattedBlocks, { at: firstBlockPath });
462
+ });
463
+ });
464
+ editor.getApi(BlockSelectionPlugin).blockSelection.focus();
771
465
  };
772
466
 
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 });
467
+ //#endregion
468
+ //#region src/react/ai-chat/transforms/insertBelowAIChat.ts
469
+ const insertBelowAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
470
+ const { toolName } = editor.getOptions(AIChatPlugin);
471
+ if (toolName === "generate") return insertBelowGenerate(editor, sourceEditor, { format });
472
+ const selectedBlocks = editor.getApi(BlockSelectionPlugin).blockSelection.getNodes();
473
+ const selectedIds = editor.getOptions(BlockSelectionPlugin).selectedIds;
474
+ editor.getTransforms(AIPlugin).ai.undo();
475
+ const insertBlocksAndSelect = editor.getTransforms(BlockSelectionPlugin).blockSelection.insertBlocksAndSelect;
476
+ if (!selectedIds || selectedIds.size === 0) return;
477
+ const lastBlock = selectedBlocks.at(-1);
478
+ if (!lastBlock) return;
479
+ const nextPath = PathApi.next(lastBlock[1]);
480
+ insertBlocksAndSelect(selectedBlocks.map((block) => block[0]), {
481
+ at: nextPath,
482
+ insertedCallback: () => {
483
+ withAIBatch(editor, () => {
484
+ acceptAISuggestions(editor);
485
+ });
486
+ }
487
+ });
488
+ editor.getApi(AIChatPlugin).aiChat.hide({ focus: false });
796
489
  };
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
- }
490
+ const insertBelowGenerate = (editor, sourceEditor, { format = "single" } = {}) => {
491
+ if (!sourceEditor || sourceEditor.api.isEmpty()) return;
492
+ const isBlockSelecting = editor.getOption(BlockSelectionPlugin, "isSelectingSome");
493
+ editor.getApi({ key: KEYS.ai }).aiChat.hide();
494
+ const insertBlocksAndSelect = editor.getTransforms(BlockSelectionPlugin).blockSelection.insertBlocksAndSelect;
495
+ if (isBlockSelecting) {
496
+ const selectedBlocks = editor.getApi(BlockSelectionPlugin).blockSelection.getNodes();
497
+ const selectedIds = editor.getOptions(BlockSelectionPlugin).selectedIds;
498
+ if (!selectedIds || selectedIds.size === 0) return;
499
+ const lastBlock = selectedBlocks.at(-1);
500
+ if (!lastBlock) return;
501
+ const nextPath = PathApi.next(lastBlock[1]);
502
+ if (format === "none") {
503
+ insertBlocksAndSelect(cloneDeep(sourceEditor.children), { at: nextPath });
504
+ return;
505
+ }
506
+ const formattedBlocks = createFormattedBlocks({
507
+ blocks: cloneDeep(sourceEditor.children),
508
+ format,
509
+ sourceBlock: lastBlock
510
+ });
511
+ if (!formattedBlocks) return;
512
+ insertBlocksAndSelect(formattedBlocks, { at: nextPath });
513
+ } else {
514
+ const [, end] = RangeApi.edges(editor.selection);
515
+ const endPath = [end.path[0]];
516
+ const currentBlock = editor.api.node({
517
+ at: endPath,
518
+ block: true,
519
+ mode: "lowest"
520
+ });
521
+ if (!currentBlock) return;
522
+ if (format === "none") {
523
+ insertBlocksAndSelect(cloneDeep(sourceEditor.children), { at: PathApi.next(endPath) });
524
+ return;
525
+ }
526
+ const formattedBlocks = createFormattedBlocks({
527
+ blocks: cloneDeep(sourceEditor.children),
528
+ format,
529
+ sourceBlock: currentBlock
530
+ });
531
+ if (!formattedBlocks) return;
532
+ insertBlocksAndSelect(formattedBlocks, { at: PathApi.next(endPath) });
533
+ }
852
534
  };
853
535
 
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
- });
536
+ //#endregion
537
+ //#region src/react/ai-chat/transforms/removeAnchorAIChat.ts
538
+ const removeAnchorAIChat = (editor, options) => {
539
+ editor.tf.withoutSaving(() => {
540
+ editor.tf.removeNodes({
541
+ at: [],
542
+ match: (n) => ElementApi.isElement(n) && n.type === getPluginType(editor, KEYS.aiChat),
543
+ ...options
544
+ });
545
+ });
864
546
  };
865
547
 
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
- };
548
+ //#endregion
549
+ //#region src/react/ai-chat/withAIChat.ts
550
+ const withAIChat = ({ api, editor, getOptions, tf: { insertText, normalizeNode }, type }) => {
551
+ const tf = editor.getTransforms(AIPlugin);
552
+ const matchesTrigger = (text) => {
553
+ const { trigger } = getOptions();
554
+ if (trigger instanceof RegExp) return trigger.test(text);
555
+ if (Array.isArray(trigger)) return trigger.includes(text);
556
+ return text === trigger;
557
+ };
558
+ return { transforms: {
559
+ insertText(text, options) {
560
+ const { triggerPreviousCharPattern, triggerQuery } = getOptions();
561
+ const fn = () => {
562
+ if (!editor.selection || !matchesTrigger(text) || triggerQuery && !triggerQuery(editor)) return;
563
+ const previousChar = editor.api.string(editor.api.range("before", editor.selection));
564
+ if (!triggerPreviousCharPattern?.test(previousChar)) return;
565
+ const nodeEntry = editor.api.block({ highest: true });
566
+ if (!nodeEntry || !editor.api.isEmpty(nodeEntry[0])) return;
567
+ api.aiChat.show();
568
+ return true;
569
+ };
570
+ if (fn()) return;
571
+ return insertText(text, options);
572
+ },
573
+ normalizeNode(entry) {
574
+ const [node, path] = entry;
575
+ if (node[KEYS.ai] && !getOptions().open) {
576
+ tf.ai.removeMarks({ at: path });
577
+ return;
578
+ }
579
+ if (ElementApi.isElement(node) && node.type === type && !getOptions().open) {
580
+ editor.getTransforms(AIChatPlugin).aiChat.removeAnchor({ at: path });
581
+ return;
582
+ }
583
+ return normalizeNode(entry);
584
+ }
585
+ } };
921
586
  };
922
587
 
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
- }
588
+ //#endregion
589
+ //#region src/react/ai-chat/AIChatPlugin.ts
590
+ const AIChatPlugin = createTPlatePlugin({
591
+ key: KEYS.aiChat,
592
+ dependencies: ["ai"],
593
+ node: { isElement: true },
594
+ options: {
595
+ _blockChunks: "",
596
+ _blockPath: null,
597
+ _mdxName: null,
598
+ _replaceIds: [],
599
+ aiEditor: null,
600
+ chat: { messages: [] },
601
+ chatNodes: [],
602
+ chatSelection: null,
603
+ experimental_lastTextId: null,
604
+ mode: "insert",
605
+ open: false,
606
+ streaming: false,
607
+ toolName: null,
608
+ trigger: " ",
609
+ triggerPreviousCharPattern: /^\s?$/
610
+ }
947
611
  }).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
- }
612
+ reset: bindFirst(resetAIChat, editor),
613
+ submit: bindFirst(submitAIChat, editor),
614
+ node: (options = {}) => {
615
+ const { anchor = false, streaming = false, ...rest } = options;
616
+ if (anchor) return editor.api.node({
617
+ at: [],
618
+ match: (n) => ElementApi.isElement(n) && n.type === type,
619
+ ...rest
620
+ });
621
+ if (streaming) {
622
+ if (!getOption("streaming")) return;
623
+ const path = getOption("_blockPath");
624
+ if (!path) return;
625
+ return editor.api.node({
626
+ at: path,
627
+ mode: "lowest",
628
+ reverse: true,
629
+ match: (t) => !!t[getPluginType(editor, KEYS.ai)],
630
+ ...rest
631
+ });
632
+ }
633
+ return editor.api.node({
634
+ match: (n) => n[getPluginType(editor, KEYS.ai)],
635
+ ...rest
636
+ });
637
+ },
638
+ reload: () => {
639
+ const { chat, chatNodes, chatSelection } = getOptions();
640
+ editor.getTransforms(AIPlugin).ai.undo();
641
+ if (chatSelection) editor.tf.setSelection(chatSelection);
642
+ else editor.getApi(BlockSelectionPlugin).blockSelection.set(chatNodes.map((node) => node.id));
643
+ const blocks = editor.getApi(BlockSelectionPlugin).blockSelection.getNodes();
644
+ const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
645
+ const ctx = {
646
+ children: editor.children,
647
+ selection: selection ?? null,
648
+ toolName: getOption("toolName")
649
+ };
650
+ chat.regenerate?.({ body: { ctx } });
651
+ },
652
+ stop: () => {
653
+ setOption("streaming", false);
654
+ getOptions().chat.stop?.();
655
+ }
1001
656
  })).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
- }
657
+ hide: ({ focus = true, undo = true } = {}) => {
658
+ api.aiChat.reset({ undo });
659
+ setOption("open", false);
660
+ if (focus) if (editor.getOption(BlockSelectionPlugin, "isSelectingSome")) editor.getApi(BlockSelectionPlugin).blockSelection.focus();
661
+ else editor.tf.focus();
662
+ const lastBatch = editor.history.undos.at(-1);
663
+ if (lastBatch?.ai) lastBatch.ai = void 0;
664
+ tf.aiChat.removeAnchor();
665
+ },
666
+ show: () => {
667
+ api.aiChat.reset();
668
+ setOption("toolName", null);
669
+ getOptions().chat.setMessages?.([]);
670
+ setOption("open", true);
671
+ }
1027
672
  })).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)
673
+ accept: bindFirst(acceptAIChat, editor),
674
+ insertBelow: bindFirst(insertBelowAIChat, editor),
675
+ removeAnchor: bindFirst(removeAnchorAIChat, editor),
676
+ replaceSelection: bindFirst(replaceSelectionAIChat, editor)
1032
677
  }));
1033
678
 
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]);
679
+ //#endregion
680
+ //#region src/react/ai-chat/hooks/useAIChatEditor.ts
681
+ /**
682
+ * Register an editor in the AI chat plugin, and deserializes the content into
683
+ * `editor.children` with block-level memoization.
684
+ */
685
+ const useAIChatEditor = (editor, content, { parser } = {}) => {
686
+ const { setOption } = useEditorPlugin(AIChatPlugin);
687
+ editor.children = useMemo(() => editor.getApi(MarkdownPlugin).markdown.deserialize(content, {
688
+ memoize: true,
689
+ parser
690
+ }), [content]);
691
+ useEffect(() => {
692
+ setOption("aiEditor", editor);
693
+ }, [editor, setOption]);
1051
694
  };
1052
695
 
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]);
696
+ //#endregion
697
+ //#region src/react/ai-chat/hooks/useChatChunk.ts
698
+ const useChatChunk = ({ onChunk, onFinish }) => {
699
+ const { status } = usePluginOption({ key: KEYS.aiChat }, "chat");
700
+ const isLoading = status === "streaming" || status === "submitted";
701
+ const content = useLastAssistantMessage()?.parts.find((part) => part.type === "text")?.text;
702
+ const insertedTextRef = useRef("");
703
+ const prevIsLoadingRef = useRef(isLoading);
704
+ useEffect(() => {
705
+ if (!isLoading) insertedTextRef.current = "";
706
+ if (prevIsLoadingRef.current && !isLoading) onFinish?.({ content: content ?? "" });
707
+ prevIsLoadingRef.current = isLoading;
708
+ }, [isLoading]);
709
+ useEffect(() => {
710
+ if (!content) return;
711
+ const chunk = content.slice(insertedTextRef.current.length);
712
+ const nodes = [];
713
+ if (chunk) {
714
+ const isFirst = insertedTextRef.current === "";
715
+ nodes.push({ text: chunk });
716
+ onChunk({
717
+ chunk,
718
+ isFirst,
719
+ nodes,
720
+ text: content
721
+ });
722
+ }
723
+ insertedTextRef.current = content;
724
+ }, [content]);
1098
725
  };
1099
726
 
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]);
727
+ //#endregion
728
+ //#region src/react/ai-chat/hooks/useEditorChat.ts
729
+ const useEditorChat = ({ onOpenBlockSelection, onOpenChange, onOpenCursor, onOpenSelection }) => {
730
+ const { editor } = useEditorPlugin(AIChatPlugin);
731
+ const open = usePluginOption(AIChatPlugin, "open");
732
+ useEffect(() => {
733
+ onOpenChange?.(open);
734
+ if (open) {
735
+ if (onOpenBlockSelection) {
736
+ const blockSelectionApi = editor.getApi(BlockSelectionPlugin).blockSelection;
737
+ if (editor.getOption(BlockSelectionPlugin, "isSelectingSome")) {
738
+ onOpenBlockSelection(blockSelectionApi.getNodes());
739
+ return;
740
+ }
741
+ }
742
+ if (onOpenCursor && editor.api.isCollapsed()) {
743
+ onOpenCursor();
744
+ return;
745
+ }
746
+ if (onOpenSelection && editor.api.isExpanded()) {
747
+ onOpenSelection();
748
+ return;
749
+ }
750
+ }
751
+ }, [open]);
1136
752
  };
1137
753
 
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");
754
+ //#endregion
755
+ //#region src/react/ai-chat/streaming/streamDeserializeInlineMd.ts
756
+ const streamDeserializeInlineMd = (editor, text, options) => editor.getApi(MarkdownPlugin).markdown.deserializeInline(text, options);
1145
757
 
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);
758
+ //#endregion
759
+ //#region src/react/ai-chat/streaming/utils/utils.ts
760
+ const getChunkTrimmed = (chunk, { direction = "right" } = {}) => {
761
+ const str = direction === "right" ? chunk.trimEnd() : chunk.trimStart();
762
+ if (direction === "right") return chunk.slice(str.length);
763
+ return chunk.slice(0, chunk.length - str.length);
1155
764
  };
1156
765
  function isCompleteCodeBlock(str) {
1157
- const trimmed = str.trim();
1158
- const startsWithCodeBlock = trimmed.startsWith("```");
1159
- const endsWithCodeBlock = trimmed.endsWith("```");
1160
- return startsWithCodeBlock && endsWithCodeBlock;
766
+ const trimmed = str.trim();
767
+ const startsWithCodeBlock = trimmed.startsWith("```");
768
+ const endsWithCodeBlock = trimmed.endsWith("```");
769
+ return startsWithCodeBlock && endsWithCodeBlock;
1161
770
  }
1162
771
  function isCompleteMath(str) {
1163
- const trimmed = str.trim();
1164
- const startsWithMath = trimmed.startsWith("$$");
1165
- const endsWithMath = trimmed.endsWith("$$");
1166
- return startsWithMath && endsWithMath;
772
+ const trimmed = str.trim();
773
+ const startsWithMath = trimmed.startsWith("$$");
774
+ const endsWithMath = trimmed.endsWith("$$");
775
+ return startsWithMath && endsWithMath;
1167
776
  }
1168
777
 
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;
778
+ //#endregion
779
+ //#region src/react/ai-chat/streaming/utils/escapeInput.ts
780
+ const escapeInput = (data) => {
781
+ let res = data;
782
+ if (data.startsWith("$$") && !data.startsWith("$$\n") && !isCompleteMath(data)) res = data.replace("$$", String.raw`\$\$`);
783
+ return res;
1176
784
  };
1177
785
 
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;
786
+ //#endregion
787
+ //#region src/react/ai-chat/streaming/utils/getListNode.ts
788
+ const getListNode = (editor, node) => {
789
+ if (node.listStyleType && node.listStart) {
790
+ const previousNode = editor.api.previous({ at: editor.selection?.focus })?.[0];
791
+ if (previousNode?.listStyleType && previousNode?.listStart) return node;
792
+ if (node.listStart === 1) return node;
793
+ return {
794
+ ...node,
795
+ listRestartPolite: node.listStart
796
+ };
797
+ }
798
+ return node;
1194
799
  };
1195
800
 
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;
801
+ //#endregion
802
+ //#region src/react/ai-chat/streaming/utils/isSameNode.ts
803
+ const LIST_STYLE_TYPE = "listStyleType";
804
+ const isSameNode = (editor, node1, node2) => {
805
+ if (node1.type !== editor.getType(KEYS.p) || node2.type !== editor.getType(KEYS.p)) return node1.type === node2.type;
806
+ if (isDefined(node1[LIST_STYLE_TYPE]) || isDefined(node2[LIST_STYLE_TYPE])) return node1[LIST_STYLE_TYPE] === node2[LIST_STYLE_TYPE];
807
+ return node1.type === node2.type;
1206
808
  };
1207
809
 
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
- };
810
+ //#endregion
811
+ //#region src/react/ai-chat/streaming/utils/nodesWithProps.ts
812
+ const nodesWithProps = (editor, nodes, options) => nodes.map((node) => {
813
+ if (ElementApi.isElement(node)) return {
814
+ ...getListNode(editor, node),
815
+ ...options.elementProps,
816
+ children: nodesWithProps(editor, node.children, options)
817
+ };
818
+ return {
819
+ ...options.textProps,
820
+ ...node,
821
+ text: node.text
822
+ };
1223
823
  });
1224
824
 
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;
825
+ //#endregion
826
+ //#region src/react/ai-chat/streaming/streamDeserializeMd.ts
827
+ const statMdxTagRegex = /<([A-Za-z][A-Za-z0-9._:-]*)(?:\s[^>]*?)?(?<!\/)>/;
828
+ const streamDeserializeMd = (editor, data, options) => {
829
+ const input = escapeInput(data);
830
+ const value = withoutDeserializeInMdx(editor, input);
831
+ if (Array.isArray(value)) return value;
832
+ let blocks = [];
833
+ blocks = editor.getApi(MarkdownPlugin).markdown.deserialize(input, {
834
+ ...options,
835
+ preserveEmptyParagraphs: false
836
+ });
837
+ const trimmedData = getChunkTrimmed(data);
838
+ const lastBlock = blocks.at(-1);
839
+ const addNewLine = trimmedData === "\n\n";
840
+ const unshiftNewLine = getChunkTrimmed(data, { direction: "left" }) === "\n\n";
841
+ const isCodeBlockOrTable = lastBlock?.type === "code_block" || lastBlock?.type === "table";
842
+ let result = blocks;
843
+ /**
844
+ * Deserialize the sting like `123\n\n` will be `123` base on markdown spec
845
+ * but we want to keep the `\n\n`
846
+ */
847
+ if (lastBlock && !isCodeBlockOrTable && trimmedData.length > 0 && !addNewLine) {
848
+ const textNode = [{ text: trimmedData }];
849
+ const lastChild = lastBlock.children.at(-1);
850
+ /** It’s like normalizing and merging the text nodes. */
851
+ if (lastChild && TextApi.isText(lastChild) && Object.keys(lastChild).length === 1) {
852
+ lastBlock.children.pop();
853
+ const textNode$1 = [{ text: lastChild.text + trimmedData }];
854
+ lastBlock.children.push(...textNode$1);
855
+ } else lastBlock.children.push(...textNode);
856
+ result = [...blocks.slice(0, -1), lastBlock];
857
+ }
858
+ if (addNewLine && !isCodeBlockOrTable) result.push({
859
+ children: [{ text: "" }],
860
+ type: KEYS.p
861
+ });
862
+ if (unshiftNewLine && !isCodeBlockOrTable) result.unshift({
863
+ children: [{ text: "" }],
864
+ type: KEYS.p
865
+ });
866
+ return result;
1275
867
  };
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
- }
868
+ const withoutDeserializeInMdx = (editor, input) => {
869
+ const mdxName = editor.getOption(AIChatPlugin, "_mdxName");
870
+ if (mdxName) {
871
+ if (input.includes(`</${mdxName}>`)) {
872
+ editor.setOption(AIChatPlugin, "_mdxName", null);
873
+ return false;
874
+ }
875
+ return [{
876
+ children: [{ text: input }],
877
+ type: getPluginType(editor, KEYS.p)
878
+ }];
879
+ }
880
+ const newMdxName = statMdxTagRegex.exec(input)?.[1];
881
+ if (input.startsWith(`<${newMdxName}`)) editor.setOption(AIChatPlugin, "_mdxName", newMdxName ?? null);
1299
882
  };
1300
883
 
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 };
884
+ //#endregion
885
+ //#region src/react/ai-chat/streaming/streamSerializeMd.ts
886
+ const trimEndHeading = (editor, value) => {
887
+ const headingKeys = new Set([
888
+ KEYS.h1,
889
+ KEYS.h2,
890
+ KEYS.h3,
891
+ KEYS.h4,
892
+ KEYS.h5,
893
+ KEYS.h6
894
+ ]);
895
+ const lastBlock = value.at(-1);
896
+ if (lastBlock && headingKeys.has(getPluginKey(editor, lastBlock.type) ?? lastBlock.type) && ElementApi.isElement(lastBlock)) {
897
+ const lastTextNode = lastBlock.children.at(-1);
898
+ if (TextApi.isText(lastTextNode)) {
899
+ const trimmedText = getChunkTrimmed(lastTextNode?.text);
900
+ const newChildren = [...lastBlock.children.slice(0, -1), { text: lastTextNode.text.trimEnd() }];
901
+ const newLastBlock = {
902
+ ...lastBlock,
903
+ children: newChildren
904
+ };
905
+ return {
906
+ trimmedText,
907
+ value: [...value.slice(0, -1), newLastBlock]
908
+ };
909
+ }
910
+ }
911
+ return {
912
+ trimmedText: "",
913
+ value
914
+ };
1338
915
  };
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;
916
+ const streamSerializeMd = (editor, options, chunk) => {
917
+ const { value: optionsValue, ...restOptions } = options;
918
+ const { value } = trimEndHeading(editor, optionsValue ?? editor.children);
919
+ let result = "";
920
+ result = editor.getApi(MarkdownPlugin).markdown.serialize({
921
+ value,
922
+ ...restOptions
923
+ });
924
+ const trimmedChunk = getChunkTrimmed(chunk);
925
+ if (isCompleteCodeBlock(result) && !chunk.endsWith("```")) result = result.trimEnd().slice(0, -3) + trimmedChunk;
926
+ if (isCompleteMath(result) && !chunk.endsWith("$$")) result = result.trimEnd().slice(0, -3) + trimmedChunk;
927
+ result = result.replace(/&#x20;/g, " ");
928
+ result = result.replace(/&#x200B;/g, " ");
929
+ result = result.replace(/\u200B/g, "");
930
+ if (trimmedChunk !== "\n\n") result = result.trimEnd() + trimmedChunk;
931
+ if (chunk.endsWith("\n\n")) {
932
+ if (result === "\n") result = "";
933
+ else if (result.endsWith("\n\n")) result = result.slice(0, -1);
934
+ }
935
+ result = result.replace(/\\([\\`*_{}\\[\]()#+\-\\.!~<>|$])/g, "$1");
936
+ return result;
1369
937
  };
1370
938
 
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;
939
+ //#endregion
940
+ //#region src/react/ai-chat/streaming/streamInsertChunk.ts
941
+ const getNextPath = (path, length) => {
942
+ let result = path;
943
+ for (let i = 0; i < length; i++) result = PathApi.next(result);
944
+ return result;
1378
945
  };
946
+ /** @experimental */
1379
947
  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
- }
948
+ const { _blockChunks, _blockPath } = editor.getOptions(AIChatPlugin);
949
+ if (_blockPath === null) {
950
+ const blocks = streamDeserializeMd(editor, chunk);
951
+ const path = getCurrentBlockPath(editor);
952
+ const startBlock = editor.api.node(path)[0];
953
+ const startInEmptyParagraph = NodeApi.string(startBlock).length === 0 && startBlock.type === getPluginType(editor, KEYS.p);
954
+ if (startInEmptyParagraph) editor.tf.removeNodes({ at: path });
955
+ if (blocks.length > 0) {
956
+ editor.tf.insertNodes(nodesWithProps(editor, [blocks[0]], options), {
957
+ at: path,
958
+ nextBlock: !startInEmptyParagraph,
959
+ select: true
960
+ });
961
+ editor.setOption(AIChatPlugin, "_blockPath", getCurrentBlockPath(editor));
962
+ editor.setOption(AIChatPlugin, "_blockChunks", chunk);
963
+ if (blocks.length > 1) {
964
+ const nextBlocks = blocks.slice(1);
965
+ const nextPath = getCurrentBlockPath(editor);
966
+ editor.tf.insertNodes(nodesWithProps(editor, nextBlocks, options), {
967
+ at: nextPath,
968
+ nextBlock: true,
969
+ select: true
970
+ });
971
+ const lastBlock = editor.api.node(getNextPath(nextPath, nextBlocks.length));
972
+ editor.setOption(AIChatPlugin, "_blockPath", lastBlock[1]);
973
+ const lastBlockChunks = streamSerializeMd(editor, { value: [lastBlock[0]] }, chunk);
974
+ editor.setOption(AIChatPlugin, "_blockChunks", lastBlockChunks);
975
+ }
976
+ }
977
+ } else {
978
+ const tempBlockChunks = _blockChunks + chunk;
979
+ const tempBlocks = streamDeserializeMd(editor, tempBlockChunks);
980
+ if (tempBlocks.length === 0) return console.warn(`unsupport md nodes: ${JSON.stringify(tempBlockChunks)}`);
981
+ if (tempBlocks.length === 1) {
982
+ const currentBlock = editor.api.node(_blockPath)[0];
983
+ if (isSameNode(editor, currentBlock, tempBlocks[0])) {
984
+ const chunkNodes = streamDeserializeInlineMd(editor, chunk);
985
+ editor.tf.insertNodes(nodesWithProps(editor, chunkNodes, options), {
986
+ at: editor.api.end(_blockPath),
987
+ select: true
988
+ });
989
+ const serializedBlock = streamSerializeMd(editor, { value: [editor.api.node(_blockPath)[0]] }, tempBlockChunks);
990
+ const blockText = NodeApi.string(tempBlocks[0]);
991
+ if (serializedBlock === tempBlockChunks && blockText === serializedBlock) editor.setOption(AIChatPlugin, "_blockChunks", tempBlockChunks);
992
+ else {
993
+ editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
994
+ at: _blockPath,
995
+ select: true
996
+ });
997
+ const serializedBlock$1 = streamSerializeMd(editor, { value: [tempBlocks[0]] }, tempBlockChunks);
998
+ 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);
999
+ }
1000
+ } else {
1001
+ const serializedBlock = streamSerializeMd(editor, { value: [tempBlocks[0]] }, tempBlockChunks);
1002
+ editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
1003
+ at: _blockPath,
1004
+ select: true
1005
+ });
1006
+ editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
1007
+ }
1008
+ } else {
1009
+ editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
1010
+ at: _blockPath,
1011
+ select: true
1012
+ });
1013
+ if (tempBlocks.length > 1) {
1014
+ const newEndBlockPath = getNextPath(_blockPath, tempBlocks.length - 1);
1015
+ editor.tf.insertNodes(nodesWithProps(editor, tempBlocks.slice(1), options), {
1016
+ at: PathApi.next(_blockPath),
1017
+ select: true
1018
+ });
1019
+ editor.setOption(AIChatPlugin, "_blockPath", newEndBlockPath);
1020
+ const endBlock = editor.api.node(newEndBlockPath)[0];
1021
+ const serializedBlock = streamSerializeMd(editor, { value: [endBlock] }, tempBlockChunks);
1022
+ editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
1023
+ }
1024
+ }
1025
+ }
1512
1026
  }
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;
1027
+ const getCurrentBlockPath = (editor) => {
1028
+ const getAnchorPreviousPath = (editor$1) => {
1029
+ const anchorNode = editor$1.getApi(AIChatPlugin).aiChat.node({ anchor: true });
1030
+ if (anchorNode) return PathApi.previous(anchorNode[1]);
1031
+ };
1032
+ const getFocusPath = (editor$1) => editor$1.selection?.focus.path.slice(0, 1);
1033
+ const path = getAnchorPreviousPath(editor) ?? getFocusPath(editor) ?? [0];
1034
+ const entry = editor.api.node(path);
1035
+ if (entry && (entry[0].type === getPluginType(editor, KEYS.columnGroup) || entry[0].type === getPluginType(editor, KEYS.table))) return editor.api.above()?.[1] ?? path;
1036
+ return path;
1527
1037
  };
1528
1038
 
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));
1039
+ //#endregion
1040
+ //#region src/react/copilot/renderCopilotBelowNodes.tsx
1041
+ const renderCopilotBelowNodes = ({ editor }) => {
1042
+ const { renderGhostText: GhostText } = getEditorPlugin(editor, { key: KEYS.copilot }).getOptions();
1043
+ if (!GhostText) return;
1044
+ return ({ children }) => /* @__PURE__ */ React.createElement(React.Fragment, null, children, /* @__PURE__ */ React.createElement(GhostText, null));
1548
1045
  };
1549
1046
 
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));
1047
+ //#endregion
1048
+ //#region src/react/copilot/transforms/acceptCopilot.ts
1049
+ const acceptCopilot = (editor) => {
1050
+ const { suggestionText } = editor.getOptions({ key: KEYS.copilot });
1051
+ if (!suggestionText?.length) return false;
1052
+ editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
1559
1053
  };
1560
1054
 
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
- }
1055
+ //#endregion
1056
+ //#region src/react/copilot/utils/callCompletionApi.ts
1057
+ const getOriginalFetch = () => fetch;
1058
+ async function callCompletionApi({ api = "/api/completion", body, credentials, fetch: fetch$1 = getOriginalFetch(), headers, prompt, setAbortController = () => {}, setCompletion = () => {}, setError = () => {}, setLoading = () => {}, onError, onFinish, onResponse }) {
1059
+ try {
1060
+ setLoading(true);
1061
+ setError(null);
1062
+ const abortController = new AbortController();
1063
+ setAbortController(abortController);
1064
+ setCompletion("");
1065
+ const res = await fetch$1(api, {
1066
+ body: JSON.stringify({
1067
+ prompt,
1068
+ ...body
1069
+ }),
1070
+ credentials,
1071
+ headers: {
1072
+ "Content-Type": "application/json",
1073
+ ...headers
1074
+ },
1075
+ method: "POST",
1076
+ signal: abortController.signal
1077
+ }).catch((error) => {
1078
+ throw error;
1079
+ });
1080
+ if (onResponse) await onResponse(res);
1081
+ if (!res.ok) throw new Error(await res.text() || "Failed to fetch the chat response.");
1082
+ if (!res.body) throw new Error("The response body is empty.");
1083
+ const { text } = await res.json();
1084
+ if (!text) throw new Error("The response does not contain a text field.");
1085
+ setCompletion(text);
1086
+ if (onFinish) onFinish(prompt, text);
1087
+ setAbortController(null);
1088
+ return text;
1089
+ } catch (error) {
1090
+ if (error.name === "AbortError") {
1091
+ setAbortController(null);
1092
+ return null;
1093
+ }
1094
+ if (error instanceof Error && onError) onError(error);
1095
+ setError(error);
1096
+ } finally {
1097
+ setLoading(false);
1098
+ }
1641
1099
  }
1642
1100
 
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 };
1101
+ //#endregion
1102
+ //#region src/react/copilot/utils/getNextWord.ts
1103
+ const nonSpaceRegex = /^\s*(\S)/;
1104
+ const cjkCharRegex = /[\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/;
1105
+ const cjkMatchRegex = /^(\s*)([\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF])([\u3000-\u303F\uFF00-\uFFEF])?/;
1106
+ const nonCjkMatchRegex = /^(\s*\S+?)(?=[\s\u1100-\u11FF\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]|$)/;
1107
+ const getNextWord = ({ text }) => {
1108
+ if (!text) return {
1109
+ firstWord: "",
1110
+ remainingText: ""
1111
+ };
1112
+ const nonSpaceMatch = nonSpaceRegex.exec(text);
1113
+ if (!nonSpaceMatch) return {
1114
+ firstWord: "",
1115
+ remainingText: ""
1116
+ };
1117
+ const firstNonSpaceChar = nonSpaceMatch[1];
1118
+ const isCJKChar = cjkCharRegex.test(firstNonSpaceChar);
1119
+ let firstWord;
1120
+ let remainingText;
1121
+ if (isCJKChar) {
1122
+ const match = cjkMatchRegex.exec(text);
1123
+ if (match) {
1124
+ const [, spaces = "", char = "", punctuation = ""] = match;
1125
+ firstWord = spaces + char + punctuation;
1126
+ remainingText = text.slice(firstWord.length);
1127
+ } else {
1128
+ firstWord = "";
1129
+ remainingText = text;
1130
+ }
1131
+ } else {
1132
+ const match = nonCjkMatchRegex.exec(text);
1133
+ if (match) {
1134
+ firstWord = match[0];
1135
+ remainingText = text.slice(firstWord.length);
1136
+ } else {
1137
+ firstWord = text;
1138
+ remainingText = "";
1139
+ }
1140
+ }
1141
+ return {
1142
+ firstWord,
1143
+ remainingText
1144
+ };
1677
1145
  };
1678
1146
 
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
- });
1147
+ //#endregion
1148
+ //#region src/react/copilot/utils/triggerCopilotSuggestion.ts
1149
+ const triggerCopilotSuggestion = async (editor) => {
1150
+ const { api, getOptions, setOption } = getEditorPlugin(editor, { key: KEYS.copilot });
1151
+ const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
1152
+ if (isLoading || editor.getOptions({ key: KEYS.aiChat }).chat?.isLoading) return false;
1153
+ if (!triggerQuery({ editor })) return false;
1154
+ const prompt = getPrompt({ editor });
1155
+ if (prompt.length === 0) return false;
1156
+ api.copilot.stop();
1157
+ await callCompletionApi({
1158
+ prompt,
1159
+ onFinish: (_, completion) => {
1160
+ api.copilot.setBlockSuggestion({ text: completion });
1161
+ },
1162
+ ...completeOptions,
1163
+ setAbortController: (controller) => setOption("abortController", controller),
1164
+ setCompletion: (completion) => setOption("completion", completion),
1165
+ setError: (error) => setOption("error", error),
1166
+ setLoading: (loading) => setOption("isLoading", loading),
1167
+ onError: (error) => {
1168
+ setOption("error", error);
1169
+ completeOptions?.onError?.(error);
1170
+ }
1171
+ });
1712
1172
  };
1713
1173
 
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);
1174
+ //#endregion
1175
+ //#region src/react/copilot/utils/withoutAbort.ts
1176
+ const withoutAbort = (editor, fn) => {
1177
+ editor.setOption(CopilotPlugin, "shouldAbort", false);
1178
+ fn();
1179
+ editor.setOption(CopilotPlugin, "shouldAbort", true);
1719
1180
  };
1720
1181
 
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
- });
1182
+ //#endregion
1183
+ //#region src/react/copilot/transforms/acceptCopilotNextWord.ts
1184
+ const acceptCopilotNextWord = (editor) => {
1185
+ const { api, getOptions } = getEditorPlugin(editor, { key: KEYS.copilot });
1186
+ const { getNextWord: getNextWord$1, suggestionText } = getOptions();
1187
+ if (!suggestionText?.length) return false;
1188
+ const { firstWord, remainingText } = getNextWord$1({ text: suggestionText });
1189
+ api.copilot.setBlockSuggestion({ text: remainingText });
1190
+ withoutAbort(editor, () => {
1191
+ editor.tf.insertFragment(deserializeInlineMd(editor, firstWord));
1192
+ });
1737
1193
  };
1738
1194
 
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;
1195
+ //#endregion
1196
+ //#region src/react/copilot/withCopilot.ts
1197
+ const getPatchString = (editor, operations) => {
1198
+ let string = "";
1199
+ for (const operation of operations) if (operation.type === "insert_node") {
1200
+ const node = operation.node;
1201
+ const text = serializeInlineMd(editor, { value: [node] });
1202
+ string += text;
1203
+ } else if (operation.type === "insert_text") string += operation.text;
1204
+ return string;
1754
1205
  };
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
- };
1206
+ const withCopilot = ({ api, editor, getOptions, setOption, tf: { apply, insertText, redo, setSelection, undo, writeHistory } }) => {
1207
+ let prevSelection = null;
1208
+ return { transforms: {
1209
+ apply(operation) {
1210
+ const { shouldAbort } = getOptions();
1211
+ if (shouldAbort) api.copilot.reject();
1212
+ apply(operation);
1213
+ },
1214
+ insertText(text, options) {
1215
+ const suggestionText = getOptions().suggestionText;
1216
+ if (suggestionText?.startsWith(text)) {
1217
+ withoutAbort(editor, () => {
1218
+ editor.tf.withoutMerging(() => {
1219
+ const newText = suggestionText?.slice(text.length);
1220
+ setOption("suggestionText", newText);
1221
+ insertText(text);
1222
+ });
1223
+ });
1224
+ return;
1225
+ }
1226
+ insertText(text, options);
1227
+ },
1228
+ redo() {
1229
+ if (!getOptions().suggestionText) return redo();
1230
+ const topRedo = editor.history.redos.at(-1);
1231
+ const prevSuggestion = getOptions().suggestionText;
1232
+ if (topRedo && topRedo.shouldAbort === false && prevSuggestion) {
1233
+ withoutAbort(editor, () => {
1234
+ const shouldRemoveText = getPatchString(editor, topRedo.operations);
1235
+ setOption("suggestionText", prevSuggestion.slice(shouldRemoveText.length));
1236
+ redo();
1237
+ });
1238
+ return;
1239
+ }
1240
+ return redo();
1241
+ },
1242
+ setSelection(props) {
1243
+ setSelection(props);
1244
+ if (editor.selection && (!prevSelection || !RangeApi.equals(prevSelection, editor.selection)) && getOptions().autoTriggerQuery({ editor }) && editor.api.isFocused()) api.copilot.triggerSuggestion();
1245
+ prevSelection = editor.selection;
1246
+ },
1247
+ undo() {
1248
+ if (!getOptions().suggestionText) return undo();
1249
+ const lastUndos = editor.history.undos.at(-1);
1250
+ const oldText = getOptions().suggestionText;
1251
+ if (lastUndos && lastUndos.shouldAbort === false && oldText) {
1252
+ withoutAbort(editor, () => {
1253
+ setOption("suggestionText", getPatchString(editor, lastUndos.operations) + oldText);
1254
+ undo();
1255
+ });
1256
+ return;
1257
+ }
1258
+ return undo();
1259
+ },
1260
+ writeHistory(stacks, batch) {
1261
+ if (!getOptions().isLoading) batch.shouldAbort = getOptions().shouldAbort;
1262
+ return writeHistory(stacks, batch);
1263
+ }
1264
+ } };
1834
1265
  };
1835
1266
 
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
- }
1267
+ //#endregion
1268
+ //#region src/react/copilot/CopilotPlugin.tsx
1269
+ const CopilotPlugin = createTPlatePlugin({
1270
+ key: KEYS.copilot,
1271
+ handlers: {
1272
+ onBlur: ({ api }) => {
1273
+ api.copilot.reject();
1274
+ },
1275
+ onMouseDown: ({ api }) => {
1276
+ api.copilot.reject();
1277
+ }
1278
+ },
1279
+ options: {
1280
+ abortController: null,
1281
+ completeOptions: {},
1282
+ completion: "",
1283
+ debounceDelay: 0,
1284
+ error: null,
1285
+ getNextWord,
1286
+ isLoading: false,
1287
+ renderGhostText: null,
1288
+ shouldAbort: true,
1289
+ suggestionNodeId: null,
1290
+ suggestionText: null,
1291
+ autoTriggerQuery: ({ editor }) => {
1292
+ if (editor.getOptions({ key: KEYS.copilot }).suggestionText) return false;
1293
+ if (editor.api.isEmpty(editor.selection, { block: true })) return false;
1294
+ const blockAbove = editor.api.block();
1295
+ if (!blockAbove) return false;
1296
+ return NodeApi.string(blockAbove[0]).at(-1) === " ";
1297
+ },
1298
+ getPrompt: ({ editor }) => {
1299
+ const contextEntry = editor.api.block({ highest: true });
1300
+ if (!contextEntry) return "";
1301
+ return serializeMd(editor, { value: [contextEntry[0]] });
1302
+ },
1303
+ triggerQuery: ({ editor }) => {
1304
+ if (editor.api.isExpanded()) return false;
1305
+ if (!editor.api.isAt({ end: true })) return false;
1306
+ return true;
1307
+ }
1308
+ }
1309
+ }).overrideEditor(withCopilot).extendSelectors(({ getOptions }) => ({ isSuggested: (id) => getOptions().suggestionNodeId === id })).extendTransforms(({ editor }) => ({
1310
+ accept: bindFirst(acceptCopilot, editor),
1311
+ acceptNextWord: bindFirst(acceptCopilotNextWord, editor)
1312
+ })).extendApi(({ api, editor, getOptions, setOption, setOptions }) => {
1313
+ const debounceDelay = getOptions().debounceDelay;
1314
+ let triggerSuggestion = bindFirst(triggerCopilotSuggestion, editor);
1315
+ if (debounceDelay) triggerSuggestion = debounce(bindFirst(triggerCopilotSuggestion, editor), debounceDelay);
1316
+ return {
1317
+ triggerSuggestion,
1318
+ setBlockSuggestion: ({ id = getOptions().suggestionNodeId, text }) => {
1319
+ if (!id) id = editor.api.block()[0].id;
1320
+ setOptions({
1321
+ suggestionNodeId: id,
1322
+ suggestionText: text
1323
+ });
1324
+ },
1325
+ stop: () => {
1326
+ const { abortController } = getOptions();
1327
+ api.copilot.triggerSuggestion?.cancel();
1328
+ if (abortController) {
1329
+ abortController.abort();
1330
+ setOption("abortController", null);
1331
+ }
1332
+ }
1333
+ };
1334
+ }).extendApi(({ api, getOptions, setOptions }) => ({ reject: () => {
1335
+ if (!getOptions().suggestionText?.length) return false;
1336
+ api.copilot.stop();
1337
+ setOptions({
1338
+ completion: null,
1339
+ suggestionNodeId: null,
1340
+ suggestionText: null
1341
+ });
1342
+ } })).extend({
1343
+ render: { belowNodes: renderCopilotBelowNodes },
1344
+ shortcuts: {
1345
+ accept: { keys: "tab" },
1346
+ reject: { keys: "escape" }
1347
+ }
1942
1348
  });
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
1349
+
1350
+ //#endregion
1351
+ 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 };