@platejs/ai 51.1.1 → 52.0.0

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