@platejs/ai 49.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.
package/dist/index.js ADDED
@@ -0,0 +1,992 @@
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/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BaseAIPlugin: () => BaseAIPlugin,
34
+ escapeInput: () => escapeInput,
35
+ getChunkTrimmed: () => getChunkTrimmed,
36
+ getCurrentBlockPath: () => getCurrentBlockPath,
37
+ getListNode: () => getListNode,
38
+ insertAINodes: () => insertAINodes,
39
+ isCompleteCodeBlock: () => isCompleteCodeBlock,
40
+ isCompleteMath: () => isCompleteMath,
41
+ isSameNode: () => isSameNode,
42
+ nodesWithProps: () => nodesWithProps,
43
+ removeAIMarks: () => removeAIMarks,
44
+ removeAINodes: () => removeAINodes,
45
+ streamDeserializeInlineMd: () => streamDeserializeInlineMd,
46
+ streamDeserializeMd: () => streamDeserializeMd,
47
+ streamInsertChunk: () => streamInsertChunk,
48
+ streamSerializeMd: () => streamSerializeMd,
49
+ undoAI: () => undoAI,
50
+ withAIBatch: () => withAIBatch
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/lib/BaseAIPlugin.ts
55
+ var import_platejs2 = require("platejs");
56
+
57
+ // src/lib/transforms/insertAINodes.ts
58
+ var insertAINodes = (editor, nodes, {
59
+ target
60
+ } = {}) => {
61
+ if (!target && !editor.selection?.focus.path) return;
62
+ const aiNodes = nodes.map((node) => ({
63
+ ...node,
64
+ ai: true
65
+ }));
66
+ editor.tf.withoutNormalizing(() => {
67
+ editor.tf.insertNodes(aiNodes, {
68
+ at: editor.api.end(target || editor.selection.focus.path),
69
+ select: true
70
+ });
71
+ editor.tf.collapse({ edge: "end" });
72
+ });
73
+ };
74
+
75
+ // src/lib/transforms/removeAIMarks.ts
76
+ var removeAIMarks = (editor, { at = [] } = {}) => {
77
+ editor.tf.unsetNodes("ai", {
78
+ at,
79
+ match: (n) => n.ai
80
+ });
81
+ };
82
+
83
+ // src/lib/transforms/removeAINodes.ts
84
+ var import_platejs = require("platejs");
85
+ var removeAINodes = (editor, { at = [] } = {}) => {
86
+ editor.tf.removeNodes({
87
+ at,
88
+ match: (n) => import_platejs.TextApi.isText(n) && !!n.ai
89
+ });
90
+ };
91
+
92
+ // src/lib/transforms/undoAI.ts
93
+ var undoAI = (editor) => {
94
+ if (editor.history.undos.at(-1)?.ai && editor.api.some({
95
+ at: [],
96
+ match: (n) => !!n.ai
97
+ })) {
98
+ editor.undo();
99
+ editor.history.redos.pop();
100
+ }
101
+ };
102
+
103
+ // src/lib/transforms/withAIBatch.ts
104
+ var withAIBatch = (editor, fn, {
105
+ split
106
+ } = {}) => {
107
+ if (split) {
108
+ editor.tf.withNewBatch(fn);
109
+ } else {
110
+ editor.tf.withMerging(fn);
111
+ }
112
+ const lastBatch = editor.history.undos?.at(-1);
113
+ if (lastBatch) {
114
+ lastBatch.ai = true;
115
+ }
116
+ };
117
+
118
+ // src/lib/BaseAIPlugin.ts
119
+ var BaseAIPlugin = (0, import_platejs2.createTSlatePlugin)({
120
+ key: import_platejs2.KEYS.ai,
121
+ node: { isDecoration: false, isLeaf: true }
122
+ }).extendTransforms(({ editor }) => ({
123
+ insertNodes: (0, import_platejs2.bindFirst)(insertAINodes, editor),
124
+ removeMarks: (0, import_platejs2.bindFirst)(removeAIMarks, editor),
125
+ removeNodes: (0, import_platejs2.bindFirst)(removeAINodes, editor),
126
+ undo: (0, import_platejs2.bindFirst)(undoAI, editor)
127
+ }));
128
+
129
+ // src/lib/streaming/streamDeserializeInlineMd.ts
130
+ var import_markdown = require("@platejs/markdown");
131
+ var streamDeserializeInlineMd = (editor, text, options) => {
132
+ return editor.getApi(import_markdown.MarkdownPlugin).markdown.deserializeInline(text, options);
133
+ };
134
+
135
+ // src/lib/streaming/streamDeserializeMd.ts
136
+ var import_markdown2 = require("@platejs/markdown");
137
+ var import_platejs5 = require("platejs");
138
+
139
+ // src/lib/streaming/utils/escapeInput.ts
140
+ var escapeInput = (data) => {
141
+ let res = data;
142
+ if (data.startsWith("$$") && !data.startsWith("$$\n") && !isCompleteMath(data)) {
143
+ res = data.replace("$$", String.raw`\$\$`);
144
+ }
145
+ return res;
146
+ };
147
+
148
+ // src/lib/streaming/utils/getListNode.ts
149
+ var getListNode = (editor, node) => {
150
+ if (node.listStyleType && node.listStart) {
151
+ const previousNode = editor.api.previous({
152
+ at: editor.selection?.focus
153
+ })?.[0];
154
+ if (previousNode?.listStyleType && previousNode?.listStart) {
155
+ return node;
156
+ } else {
157
+ if (node.listStart === 1) return node;
158
+ return {
159
+ ...node,
160
+ listRestartPolite: node.listStart
161
+ };
162
+ }
163
+ }
164
+ return node;
165
+ };
166
+
167
+ // src/lib/streaming/utils/isSameNode.ts
168
+ var import_platejs3 = require("platejs");
169
+ var LIST_STYLE_TYPE = "listStyleType";
170
+ var isSameNode = (editor, node1, node2) => {
171
+ if (node1.type !== editor.getType(import_platejs3.KEYS.p) || node2.type !== editor.getType(import_platejs3.KEYS.p))
172
+ return node1.type === node2.type;
173
+ if ((0, import_platejs3.isDefined)(node1[LIST_STYLE_TYPE]) || (0, import_platejs3.isDefined)(node2[LIST_STYLE_TYPE])) {
174
+ return node1[LIST_STYLE_TYPE] === node2[LIST_STYLE_TYPE];
175
+ }
176
+ return node1.type === node2.type;
177
+ };
178
+
179
+ // src/lib/streaming/utils/nodesWithProps.ts
180
+ var import_platejs4 = require("platejs");
181
+ var nodesWithProps = (editor, nodes, options) => {
182
+ return nodes.map((node) => {
183
+ if (import_platejs4.ElementApi.isElement(node)) {
184
+ return {
185
+ ...getListNode(editor, node),
186
+ ...options.elementProps,
187
+ children: nodesWithProps(editor, node.children, options)
188
+ };
189
+ } else {
190
+ return {
191
+ ...options.textProps,
192
+ ...node,
193
+ text: node.text
194
+ };
195
+ }
196
+ });
197
+ };
198
+
199
+ // src/lib/streaming/utils/utils.ts
200
+ var getChunkTrimmed = (chunk, {
201
+ direction = "right"
202
+ } = {}) => {
203
+ const str = direction === "right" ? chunk.trimEnd() : chunk.trimStart();
204
+ if (direction === "right") {
205
+ return chunk.slice(str.length);
206
+ } else {
207
+ return chunk.slice(0, chunk.length - str.length);
208
+ }
209
+ };
210
+ function isCompleteCodeBlock(str) {
211
+ const trimmed = str.trim();
212
+ const startsWithCodeBlock = trimmed.startsWith("```");
213
+ const endsWithCodeBlock = trimmed.endsWith("```");
214
+ return startsWithCodeBlock && endsWithCodeBlock;
215
+ }
216
+ function isCompleteMath(str) {
217
+ const trimmed = str.trim();
218
+ const startsWithMath = trimmed.startsWith("$$");
219
+ const endsWithMath = trimmed.endsWith("$$");
220
+ return startsWithMath && endsWithMath;
221
+ }
222
+
223
+ // src/lib/streaming/streamDeserializeMd.ts
224
+ var streamDeserializeMd = (editor, data, options) => {
225
+ const input = escapeInput(data);
226
+ let blocks = [];
227
+ blocks = editor.getApi(import_markdown2.MarkdownPlugin).markdown.deserialize(input, options);
228
+ const trimmedData = getChunkTrimmed(data);
229
+ const lastBlock = blocks.at(-1);
230
+ const addNewLine = trimmedData === "\n\n";
231
+ const unshiftNewLine = getChunkTrimmed(data, { direction: "left" }) === "\n\n";
232
+ const isCodeBlockOrTable = lastBlock?.type === "code_block" || lastBlock?.type === "table";
233
+ let result = blocks;
234
+ if (lastBlock && !isCodeBlockOrTable && trimmedData.length > 0 && !addNewLine) {
235
+ const textNode = [
236
+ {
237
+ text: trimmedData
238
+ }
239
+ ];
240
+ const lastChild = lastBlock.children.at(-1);
241
+ if (lastChild && import_platejs5.TextApi.isText(lastChild) && Object.keys(lastChild).length === 1) {
242
+ lastBlock.children.pop();
243
+ const textNode2 = [
244
+ {
245
+ text: lastChild.text + trimmedData
246
+ }
247
+ ];
248
+ lastBlock.children.push(...textNode2);
249
+ } else {
250
+ lastBlock.children.push(...textNode);
251
+ }
252
+ result = [...blocks.slice(0, -1), lastBlock];
253
+ }
254
+ if (addNewLine && !isCodeBlockOrTable) {
255
+ result.push({
256
+ children: [{ text: "" }],
257
+ type: import_platejs5.KEYS.p
258
+ });
259
+ }
260
+ if (unshiftNewLine && !isCodeBlockOrTable) {
261
+ result.unshift({
262
+ children: [{ text: "" }],
263
+ type: import_platejs5.KEYS.p
264
+ });
265
+ }
266
+ return result;
267
+ };
268
+
269
+ // src/lib/streaming/streamInsertChunk.ts
270
+ var import_platejs16 = require("platejs");
271
+
272
+ // src/react/ai/AIPlugin.ts
273
+ var import_react = require("platejs/react");
274
+ var AIPlugin = (0, import_react.toPlatePlugin)(BaseAIPlugin);
275
+
276
+ // src/react/ai-chat/AIChatPlugin.ts
277
+ var import_react9 = require("@platejs/selection/react");
278
+ var import_platejs14 = require("platejs");
279
+ var import_react10 = require("platejs/react");
280
+
281
+ // src/react/ai-chat/transforms/acceptAIChat.ts
282
+ var import_platejs6 = require("platejs");
283
+ var import_react2 = require("platejs/react");
284
+ var acceptAIChat = (editor) => {
285
+ const { tf } = (0, import_react2.getEditorPlugin)(editor, AIPlugin);
286
+ const api = editor.getApi({ key: import_platejs6.KEYS.ai });
287
+ const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
288
+ withAIBatch(editor, () => {
289
+ tf.ai.removeMarks();
290
+ editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
291
+ });
292
+ api.aiChat.hide();
293
+ editor.tf.focus();
294
+ const focusPoint = editor.api.end(lastAINodePath);
295
+ editor.tf.setSelection({
296
+ anchor: focusPoint,
297
+ focus: focusPoint
298
+ });
299
+ };
300
+
301
+ // src/react/ai-chat/transforms/insertBelowAIChat.ts
302
+ var import_react4 = require("@platejs/selection/react");
303
+ var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"));
304
+ var import_platejs8 = require("platejs");
305
+
306
+ // src/react/ai-chat/transforms/replaceSelectionAIChat.ts
307
+ var import_react3 = require("@platejs/selection/react");
308
+ var import_cloneDeep = __toESM(require("lodash/cloneDeep.js"));
309
+ var import_platejs7 = require("platejs");
310
+ var createFormattedBlocks = ({
311
+ blocks,
312
+ format,
313
+ sourceBlock
314
+ }) => {
315
+ if (format === "none") return (0, import_cloneDeep.default)(blocks);
316
+ const [sourceNode] = sourceBlock;
317
+ const firstTextEntry = import_platejs7.NodeApi.firstText(sourceNode);
318
+ if (!firstTextEntry) return null;
319
+ const blockProps = import_platejs7.NodeApi.extractProps(sourceNode);
320
+ const textProps = import_platejs7.NodeApi.extractProps(firstTextEntry[0]);
321
+ const applyTextFormatting = (node) => {
322
+ if (import_platejs7.TextApi.isText(node)) {
323
+ return { ...textProps, ...node };
324
+ }
325
+ if (node.children) {
326
+ return {
327
+ ...node,
328
+ children: node.children.map(applyTextFormatting)
329
+ };
330
+ }
331
+ return node;
332
+ };
333
+ return blocks.map((block, index) => {
334
+ if (format === "single" && index > 0) {
335
+ return block;
336
+ }
337
+ return applyTextFormatting({
338
+ ...block,
339
+ ...blockProps
340
+ });
341
+ });
342
+ };
343
+ var replaceSelectionAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
344
+ if (!sourceEditor || sourceEditor.api.isEmpty()) return;
345
+ const isBlockSelecting = editor.getOption(
346
+ import_react3.BlockSelectionPlugin,
347
+ "isSelectingSome"
348
+ );
349
+ editor.getApi({ key: import_platejs7.KEYS.ai }).aiChat.hide();
350
+ if (!isBlockSelecting) {
351
+ const firstBlock = editor.api.node({
352
+ block: true,
353
+ mode: "lowest"
354
+ });
355
+ if (firstBlock && editor.api.isSelected(firstBlock[1], { contains: true }) && format !== "none") {
356
+ const formattedBlocks2 = createFormattedBlocks({
357
+ blocks: (0, import_cloneDeep.default)(sourceEditor.children),
358
+ format,
359
+ sourceBlock: firstBlock
360
+ });
361
+ if (!formattedBlocks2) return;
362
+ editor.tf.insertFragment(formattedBlocks2);
363
+ editor.tf.focus();
364
+ return;
365
+ }
366
+ editor.tf.insertFragment(sourceEditor.children);
367
+ editor.tf.focus();
368
+ return;
369
+ }
370
+ const blockSelectionApi = editor.getApi(import_react3.BlockSelectionPlugin).blockSelection;
371
+ const selectedBlocks = blockSelectionApi.getNodes();
372
+ if (selectedBlocks.length === 0) return;
373
+ if (format === "none" || format === "single" && selectedBlocks.length > 1) {
374
+ editor.tf.withoutNormalizing(() => {
375
+ (0, import_react3.removeBlockSelectionNodes)(editor);
376
+ editor.tf.withNewBatch(() => {
377
+ editor.getTransforms(import_react3.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(
378
+ (0, import_cloneDeep.default)(sourceEditor.children),
379
+ {
380
+ at: selectedBlocks[0][1]
381
+ }
382
+ );
383
+ });
384
+ });
385
+ editor.getApi(import_react3.BlockSelectionPlugin).blockSelection.focus();
386
+ return;
387
+ }
388
+ const [, firstBlockPath] = selectedBlocks[0];
389
+ const formattedBlocks = createFormattedBlocks({
390
+ blocks: (0, import_cloneDeep.default)(sourceEditor.children),
391
+ format,
392
+ sourceBlock: selectedBlocks[0]
393
+ });
394
+ if (!formattedBlocks) return;
395
+ editor.tf.withoutNormalizing(() => {
396
+ (0, import_react3.removeBlockSelectionNodes)(editor);
397
+ editor.tf.withNewBatch(() => {
398
+ editor.getTransforms(import_react3.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect(formattedBlocks, {
399
+ at: firstBlockPath
400
+ });
401
+ });
402
+ });
403
+ editor.getApi(import_react3.BlockSelectionPlugin).blockSelection.focus();
404
+ };
405
+
406
+ // src/react/ai-chat/transforms/insertBelowAIChat.ts
407
+ var insertBelowAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
408
+ if (!sourceEditor || sourceEditor.api.isEmpty()) return;
409
+ const isBlockSelecting = editor.getOption(
410
+ import_react4.BlockSelectionPlugin,
411
+ "isSelectingSome"
412
+ );
413
+ editor.getApi({ key: import_platejs8.KEYS.ai }).aiChat.hide();
414
+ const insertBlocksAndSelect = editor.getTransforms(import_react4.BlockSelectionPlugin).blockSelection.insertBlocksAndSelect;
415
+ if (isBlockSelecting) {
416
+ const selectedBlocks = editor.getApi(import_react4.BlockSelectionPlugin).blockSelection.getNodes();
417
+ const selectedIds = editor.getOptions(import_react4.BlockSelectionPlugin).selectedIds;
418
+ if (!selectedIds || selectedIds.size === 0) return;
419
+ const lastBlock = selectedBlocks.at(-1);
420
+ if (!lastBlock) return;
421
+ const nextPath = import_platejs8.PathApi.next(lastBlock[1]);
422
+ if (format === "none") {
423
+ insertBlocksAndSelect((0, import_cloneDeep2.default)(sourceEditor.children), {
424
+ at: nextPath
425
+ });
426
+ return;
427
+ }
428
+ const formattedBlocks = createFormattedBlocks({
429
+ blocks: (0, import_cloneDeep2.default)(sourceEditor.children),
430
+ format,
431
+ sourceBlock: lastBlock
432
+ });
433
+ if (!formattedBlocks) return;
434
+ insertBlocksAndSelect(formattedBlocks, {
435
+ at: nextPath
436
+ });
437
+ } else {
438
+ const [, end] = import_platejs8.RangeApi.edges(editor.selection);
439
+ const endPath = [end.path[0]];
440
+ const currentBlock = editor.api.node({
441
+ at: endPath,
442
+ block: true,
443
+ mode: "lowest"
444
+ });
445
+ if (!currentBlock) return;
446
+ if (format === "none") {
447
+ insertBlocksAndSelect((0, import_cloneDeep2.default)(sourceEditor.children), {
448
+ at: import_platejs8.PathApi.next(endPath)
449
+ });
450
+ return;
451
+ }
452
+ const formattedBlocks = createFormattedBlocks({
453
+ blocks: (0, import_cloneDeep2.default)(sourceEditor.children),
454
+ format,
455
+ sourceBlock: currentBlock
456
+ });
457
+ if (!formattedBlocks) return;
458
+ insertBlocksAndSelect(formattedBlocks, {
459
+ at: import_platejs8.PathApi.next(endPath)
460
+ });
461
+ }
462
+ };
463
+
464
+ // src/react/ai-chat/transforms/removeAnchorAIChat.ts
465
+ var import_platejs9 = require("platejs");
466
+ var removeAnchorAIChat = (editor, options) => {
467
+ editor.tf.withoutSaving(() => {
468
+ editor.tf.removeNodes({
469
+ at: [],
470
+ match: (n) => import_platejs9.ElementApi.isElement(n) && n.type === import_platejs9.KEYS.aiChat,
471
+ ...options
472
+ });
473
+ });
474
+ };
475
+
476
+ // src/react/ai-chat/utils/getEditorPrompt.ts
477
+ var import_selection = require("@platejs/selection");
478
+ var import_react6 = require("@platejs/selection/react");
479
+
480
+ // src/react/ai-chat/utils/getMarkdown.ts
481
+ var import_markdown3 = require("@platejs/markdown");
482
+ var import_react5 = require("@platejs/selection/react");
483
+ var import_platejs10 = require("platejs");
484
+ var getMarkdown = (editor, type) => {
485
+ if (type === "editor") {
486
+ return (0, import_markdown3.serializeMd)(editor);
487
+ }
488
+ if (type === "block") {
489
+ const blocks = editor.getOption(import_react5.BlockSelectionPlugin, "isSelectingSome") ? editor.getApi(import_react5.BlockSelectionPlugin).blockSelection.getNodes() : editor.api.nodes({
490
+ mode: "highest",
491
+ match: (n) => editor.api.isBlock(n)
492
+ });
493
+ const nodes = Array.from(blocks, (entry) => entry[0]);
494
+ return (0, import_markdown3.serializeMd)(editor, { value: nodes });
495
+ }
496
+ if (type === "selection") {
497
+ const fragment = editor.api.fragment();
498
+ if (fragment.length === 1) {
499
+ const modifiedFragment = [
500
+ {
501
+ children: fragment[0].children,
502
+ type: import_platejs10.KEYS.p
503
+ }
504
+ ];
505
+ return (0, import_markdown3.serializeMd)(editor, { value: modifiedFragment });
506
+ }
507
+ return (0, import_markdown3.serializeMd)(editor, { value: fragment });
508
+ }
509
+ return "";
510
+ };
511
+
512
+ // src/react/ai-chat/utils/getEditorPrompt.ts
513
+ var replacePlaceholders = (editor, text, {
514
+ prompt
515
+ }) => {
516
+ let result = text.replace("{prompt}", prompt || "");
517
+ const placeholders = {
518
+ "{block}": "block",
519
+ "{editor}": "editor",
520
+ "{selection}": "selection"
521
+ };
522
+ Object.entries(placeholders).forEach(([placeholder, type]) => {
523
+ if (result.includes(placeholder)) {
524
+ result = result.replace(placeholder, getMarkdown(editor, type));
525
+ }
526
+ });
527
+ return result;
528
+ };
529
+ var createPromptFromConfig = (config, params) => {
530
+ const { isBlockSelecting, isSelecting: isSelecting3 } = params;
531
+ if (isBlockSelecting && config.blockSelecting) {
532
+ return config.blockSelecting ?? config.default;
533
+ } else if (isSelecting3 && config.selecting) {
534
+ return config.selecting ?? config.default;
535
+ } else {
536
+ return config.default;
537
+ }
538
+ };
539
+ var getEditorPrompt = (editor, {
540
+ prompt = "",
541
+ promptTemplate = () => "{prompt}"
542
+ } = {}) => {
543
+ const params = {
544
+ editor,
545
+ isBlockSelecting: editor.getOption(import_react6.BlockSelectionPlugin, "isSelectingSome"),
546
+ isSelecting: (0, import_selection.isSelecting)(editor)
547
+ };
548
+ const template = promptTemplate(params);
549
+ if (!template) return;
550
+ let promptText = "";
551
+ if (typeof prompt === "function") {
552
+ promptText = prompt(params);
553
+ } else if (typeof prompt === "object") {
554
+ promptText = createPromptFromConfig(prompt, params);
555
+ } else {
556
+ promptText = prompt;
557
+ }
558
+ return replacePlaceholders(editor, template, {
559
+ prompt: promptText
560
+ });
561
+ };
562
+
563
+ // src/react/ai-chat/utils/resetAIChat.ts
564
+ var import_platejs11 = require("platejs");
565
+ var import_react7 = require("platejs/react");
566
+ var resetAIChat = (editor) => {
567
+ const { api, getOptions } = (0, import_react7.getEditorPlugin)(editor, {
568
+ key: import_platejs11.KEYS.aiChat
569
+ });
570
+ api.aiChat.stop();
571
+ const chat = getOptions().chat;
572
+ if (chat.messages && chat.messages.length > 0) {
573
+ chat.setMessages?.([]);
574
+ }
575
+ editor.getTransforms(AIPlugin).ai.undo();
576
+ };
577
+
578
+ // src/react/ai-chat/utils/submitAIChat.ts
579
+ var import_selection2 = require("@platejs/selection");
580
+ var import_platejs12 = require("platejs");
581
+ var import_react8 = require("platejs/react");
582
+ var submitAIChat = (editor, {
583
+ mode,
584
+ options,
585
+ prompt,
586
+ system
587
+ } = {}) => {
588
+ const { getOptions, setOption } = (0, import_react8.getEditorPlugin)(
589
+ editor,
590
+ {
591
+ key: import_platejs12.KEYS.aiChat
592
+ }
593
+ );
594
+ const { chat, promptTemplate, systemTemplate } = getOptions();
595
+ if (!prompt && chat.input?.length === 0) {
596
+ return;
597
+ }
598
+ if (!prompt) {
599
+ prompt = chat.input;
600
+ }
601
+ if (!mode) {
602
+ mode = (0, import_selection2.isSelecting)(editor) ? "chat" : "insert";
603
+ }
604
+ if (mode === "insert") {
605
+ editor.getTransforms(AIPlugin).ai.undo();
606
+ }
607
+ setOption("mode", mode);
608
+ chat.setInput?.("");
609
+ void chat.append?.(
610
+ {
611
+ content: getEditorPrompt(editor, {
612
+ prompt,
613
+ promptTemplate
614
+ }) ?? "",
615
+ role: "user"
616
+ },
617
+ {
618
+ body: {
619
+ system: getEditorPrompt(editor, {
620
+ prompt: system,
621
+ promptTemplate: systemTemplate
622
+ })
623
+ },
624
+ ...options
625
+ }
626
+ );
627
+ };
628
+
629
+ // src/react/ai-chat/withAIChat.ts
630
+ var import_platejs13 = require("platejs");
631
+ var withAIChat = ({
632
+ api,
633
+ editor,
634
+ getOptions,
635
+ tf: { insertText, normalizeNode, setSelection },
636
+ type
637
+ }) => {
638
+ const tf = editor.getTransforms(AIPlugin);
639
+ const matchesTrigger = (text) => {
640
+ const { trigger } = getOptions();
641
+ if (trigger instanceof RegExp) {
642
+ return trigger.test(text);
643
+ }
644
+ if (Array.isArray(trigger)) {
645
+ return trigger.includes(text);
646
+ }
647
+ return text === trigger;
648
+ };
649
+ return {
650
+ transforms: {
651
+ insertText(text, options) {
652
+ const { triggerPreviousCharPattern, triggerQuery } = getOptions();
653
+ const fn = () => {
654
+ if (!editor.selection || !matchesTrigger(text) || triggerQuery && !triggerQuery(editor)) {
655
+ return;
656
+ }
657
+ const previousChar = editor.api.string(
658
+ editor.api.range("before", editor.selection)
659
+ );
660
+ const matchesPreviousCharPattern = triggerPreviousCharPattern?.test(previousChar);
661
+ if (!matchesPreviousCharPattern) return;
662
+ const nodeEntry = editor.api.block({ highest: true });
663
+ if (!nodeEntry || !editor.api.isEmpty(nodeEntry[0])) return;
664
+ api.aiChat.show();
665
+ return true;
666
+ };
667
+ if (fn()) return;
668
+ return insertText(text, options);
669
+ },
670
+ normalizeNode(entry) {
671
+ const [node, path] = entry;
672
+ if (node[import_platejs13.KEYS.ai] && !getOptions().open) {
673
+ tf.ai.removeMarks({ at: path });
674
+ return;
675
+ }
676
+ if (import_platejs13.ElementApi.isElement(node) && node.type === type && !getOptions().open) {
677
+ editor.getTransforms(AIChatPlugin).aiChat.removeAnchor({ at: path });
678
+ return;
679
+ }
680
+ return normalizeNode(entry);
681
+ }
682
+ }
683
+ };
684
+ };
685
+
686
+ // src/react/ai-chat/AIChatPlugin.ts
687
+ var AIChatPlugin = (0, import_react10.createTPlatePlugin)({
688
+ key: import_platejs14.KEYS.aiChat,
689
+ dependencies: ["ai"],
690
+ node: {
691
+ isElement: true
692
+ },
693
+ options: {
694
+ _blockChunks: "",
695
+ _blockPath: null,
696
+ aiEditor: null,
697
+ chat: { messages: [] },
698
+ experimental_lastTextId: null,
699
+ mode: "chat",
700
+ open: false,
701
+ streaming: false,
702
+ trigger: " ",
703
+ triggerPreviousCharPattern: /^\s?$/,
704
+ promptTemplate: () => "{prompt}",
705
+ systemTemplate: () => {
706
+ }
707
+ }
708
+ }).overrideEditor(withAIChat).extendApi(({ editor, getOption, getOptions, setOption, type }) => {
709
+ return {
710
+ reset: (0, import_platejs14.bindFirst)(resetAIChat, editor),
711
+ submit: (0, import_platejs14.bindFirst)(submitAIChat, editor),
712
+ node: (options = {}) => {
713
+ const { anchor = false, streaming = false, ...rest } = options;
714
+ if (anchor) {
715
+ return editor.api.node({
716
+ at: [],
717
+ match: (n) => import_platejs14.ElementApi.isElement(n) && n.type === type,
718
+ ...rest
719
+ });
720
+ }
721
+ if (streaming) {
722
+ if (!getOption("streaming")) return;
723
+ const path = getOption("_blockPath");
724
+ if (!path) return;
725
+ return editor.api.node({
726
+ at: path,
727
+ mode: "lowest",
728
+ reverse: true,
729
+ match: (t) => !!t.ai,
730
+ ...rest
731
+ });
732
+ }
733
+ return editor.api.node({
734
+ match: (n) => n.ai,
735
+ ...rest
736
+ });
737
+ },
738
+ reload: () => {
739
+ const { chat, mode } = getOptions();
740
+ if (mode === "insert") {
741
+ editor.getTransforms(AIPlugin).ai.undo();
742
+ }
743
+ void chat.reload?.({
744
+ body: {
745
+ system: getEditorPrompt(editor, {
746
+ promptTemplate: getOptions().systemTemplate
747
+ })
748
+ }
749
+ });
750
+ },
751
+ stop: () => {
752
+ setOption("streaming", false);
753
+ getOptions().chat.stop?.();
754
+ }
755
+ };
756
+ }).extendApi(({ api, editor, getOptions, setOption, tf, type }) => ({
757
+ hide: () => {
758
+ api.aiChat.reset();
759
+ setOption("open", false);
760
+ if (editor.getOption(import_react9.BlockSelectionPlugin, "isSelectingSome")) {
761
+ } else {
762
+ editor.tf.focus();
763
+ }
764
+ const lastBatch = editor.history.undos.at(-1);
765
+ if (lastBatch?.ai) {
766
+ delete lastBatch.ai;
767
+ }
768
+ tf.aiChat.removeAnchor();
769
+ },
770
+ show: () => {
771
+ api.aiChat.reset();
772
+ getOptions().chat.setMessages?.([]);
773
+ setOption("open", true);
774
+ }
775
+ })).extendTransforms(({ editor }) => ({
776
+ accept: (0, import_platejs14.bindFirst)(acceptAIChat, editor),
777
+ insertBelow: (0, import_platejs14.bindFirst)(insertBelowAIChat, editor),
778
+ removeAnchor: (0, import_platejs14.bindFirst)(removeAnchorAIChat, editor),
779
+ replaceSelection: (0, import_platejs14.bindFirst)(replaceSelectionAIChat, editor)
780
+ }));
781
+
782
+ // src/lib/streaming/streamSerializeMd.ts
783
+ var import_markdown4 = require("@platejs/markdown");
784
+ var import_platejs15 = require("platejs");
785
+ var trimEndHeading = (value) => {
786
+ const headingKeys = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
787
+ const lastBlock = value.at(-1);
788
+ if (lastBlock && headingKeys.has(lastBlock.type) && import_platejs15.ElementApi.isElement(lastBlock)) {
789
+ const lastTextNode = lastBlock.children.at(-1);
790
+ if (import_platejs15.TextApi.isText(lastTextNode)) {
791
+ const trimmedText = getChunkTrimmed(lastTextNode?.text);
792
+ const newChildren = [
793
+ ...lastBlock.children.slice(0, -1),
794
+ { text: lastTextNode.text.trimEnd() }
795
+ ];
796
+ const newLastBlock = {
797
+ ...lastBlock,
798
+ children: newChildren
799
+ };
800
+ return {
801
+ trimmedText,
802
+ value: [...value.slice(0, -1), newLastBlock]
803
+ };
804
+ }
805
+ }
806
+ return { trimmedText: "", value };
807
+ };
808
+ var streamSerializeMd = (editor, options, chunk) => {
809
+ const { value: optionsValue, ...restOptions } = options;
810
+ const { value } = trimEndHeading(optionsValue ?? editor.children);
811
+ let result = "";
812
+ result = editor.getApi(import_markdown4.MarkdownPlugin).markdown.serialize({
813
+ value,
814
+ ...restOptions
815
+ });
816
+ const trimmedChunk = getChunkTrimmed(chunk);
817
+ if (isCompleteCodeBlock(result) && !chunk.endsWith("```")) {
818
+ result = result.trimEnd().slice(0, -3) + trimmedChunk;
819
+ }
820
+ if (isCompleteMath(result) && !chunk.endsWith("$$")) {
821
+ result = result.trimEnd().slice(0, -3) + trimmedChunk;
822
+ }
823
+ result = result.replace(/ /g, " ");
824
+ result = result.replace(/​/g, " ");
825
+ if (trimmedChunk !== "\n\n") {
826
+ result = result.trimEnd() + trimmedChunk;
827
+ }
828
+ result = result.replace(/\\([\\`*_{}\\[\]()#+\-\\.!~<>|$])/g, "$1");
829
+ return result;
830
+ };
831
+
832
+ // src/lib/streaming/streamInsertChunk.ts
833
+ function streamInsertChunk(editor, chunk, options = {}) {
834
+ const { _blockChunks, _blockPath } = editor.getOptions(AIChatPlugin);
835
+ if (_blockPath === null) {
836
+ const blocks = streamDeserializeMd(editor, chunk);
837
+ const path = getCurrentBlockPath(editor);
838
+ const startInEmptyParagraph = import_platejs16.NodeApi.string(editor.api.node(path)[0]).length === 0;
839
+ if (startInEmptyParagraph) {
840
+ editor.tf.removeNodes({ at: path });
841
+ }
842
+ if (blocks.length > 0) {
843
+ editor.tf.insertNodes(nodesWithProps(editor, [blocks[0]], options), {
844
+ at: path,
845
+ nextBlock: !startInEmptyParagraph,
846
+ select: true
847
+ });
848
+ editor.setOption(AIChatPlugin, "_blockPath", getCurrentBlockPath(editor));
849
+ editor.setOption(AIChatPlugin, "_blockChunks", chunk);
850
+ if (blocks.length > 1) {
851
+ const nextBlocks = blocks.slice(1);
852
+ const nextPath = getCurrentBlockPath(editor);
853
+ editor.tf.insertNodes(nodesWithProps(editor, nextBlocks, options), {
854
+ at: nextPath,
855
+ nextBlock: true,
856
+ select: true
857
+ });
858
+ const lastBlock = editor.api.node(nextBlocks.at(-1)) ?? editor.api.node([nextPath[0] + nextBlocks.length]);
859
+ editor.setOption(AIChatPlugin, "_blockPath", lastBlock[1]);
860
+ const lastBlockChunks = streamSerializeMd(
861
+ editor,
862
+ {
863
+ value: [lastBlock[0]]
864
+ },
865
+ chunk
866
+ );
867
+ editor.setOption(AIChatPlugin, "_blockChunks", lastBlockChunks);
868
+ }
869
+ }
870
+ } else {
871
+ const tempBlockChunks = _blockChunks + chunk;
872
+ const tempBlocks = streamDeserializeMd(editor, tempBlockChunks);
873
+ if (tempBlocks.length === 0) {
874
+ return console.warn(
875
+ `unsupport md nodes: ${JSON.stringify(tempBlockChunks)}`
876
+ );
877
+ }
878
+ if (tempBlocks.length === 1) {
879
+ const currentBlock = editor.api.node(_blockPath)[0];
880
+ if (isSameNode(editor, currentBlock, tempBlocks[0])) {
881
+ const chunkNodes = streamDeserializeInlineMd(editor, chunk);
882
+ editor.tf.insertNodes(nodesWithProps(editor, chunkNodes, options), {
883
+ at: editor.api.end(_blockPath),
884
+ select: true
885
+ });
886
+ const updatedBlock = editor.api.node(_blockPath);
887
+ const serializedBlock = streamSerializeMd(
888
+ editor,
889
+ {
890
+ value: [updatedBlock[0]]
891
+ },
892
+ tempBlockChunks
893
+ );
894
+ const blockText = import_platejs16.NodeApi.string(tempBlocks[0]);
895
+ if (serializedBlock === tempBlockChunks && blockText === serializedBlock) {
896
+ editor.setOption(AIChatPlugin, "_blockChunks", tempBlockChunks);
897
+ } else {
898
+ editor.tf.replaceNodes(
899
+ nodesWithProps(editor, [tempBlocks[0]], options),
900
+ {
901
+ at: _blockPath,
902
+ select: true
903
+ }
904
+ );
905
+ const serializedBlock2 = streamSerializeMd(
906
+ editor,
907
+ {
908
+ value: [tempBlocks[0]]
909
+ },
910
+ tempBlockChunks
911
+ );
912
+ editor.setOption(
913
+ AIChatPlugin,
914
+ "_blockChunks",
915
+ // one block includes multiple children
916
+ tempBlocks[0].type === "code_block" || tempBlocks[0].type === "table" || tempBlocks[0].type === "equation" ? tempBlockChunks : serializedBlock2
917
+ );
918
+ }
919
+ } else {
920
+ const serializedBlock = streamSerializeMd(
921
+ editor,
922
+ {
923
+ value: [tempBlocks[0]]
924
+ },
925
+ tempBlockChunks
926
+ );
927
+ editor.tf.replaceNodes(
928
+ nodesWithProps(editor, [tempBlocks[0]], options),
929
+ {
930
+ at: _blockPath,
931
+ select: true
932
+ }
933
+ );
934
+ editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
935
+ }
936
+ } else {
937
+ editor.tf.replaceNodes(nodesWithProps(editor, [tempBlocks[0]], options), {
938
+ at: _blockPath,
939
+ select: true
940
+ });
941
+ if (tempBlocks.length > 1) {
942
+ const newEndBlockPath = [_blockPath[0] + tempBlocks.length - 1];
943
+ editor.tf.insertNodes(
944
+ nodesWithProps(editor, tempBlocks.slice(1), options),
945
+ {
946
+ at: import_platejs16.PathApi.next(_blockPath),
947
+ select: true
948
+ }
949
+ );
950
+ editor.setOption(AIChatPlugin, "_blockPath", newEndBlockPath);
951
+ const endBlock = editor.api.node(newEndBlockPath)[0];
952
+ const serializedBlock = streamSerializeMd(
953
+ editor,
954
+ {
955
+ value: [endBlock]
956
+ },
957
+ tempBlockChunks
958
+ );
959
+ editor.setOption(AIChatPlugin, "_blockChunks", serializedBlock);
960
+ }
961
+ }
962
+ }
963
+ }
964
+ var getCurrentBlockPath = (editor) => {
965
+ const anchorNode = editor.getApi(AIChatPlugin).aiChat.node({ anchor: true });
966
+ if (anchorNode) {
967
+ return import_platejs16.PathApi.previous(anchorNode[1]);
968
+ }
969
+ return editor.selection?.focus.path.slice(0, 1) ?? [0];
970
+ };
971
+ // Annotate the CommonJS export names for ESM import in node:
972
+ 0 && (module.exports = {
973
+ BaseAIPlugin,
974
+ escapeInput,
975
+ getChunkTrimmed,
976
+ getCurrentBlockPath,
977
+ getListNode,
978
+ insertAINodes,
979
+ isCompleteCodeBlock,
980
+ isCompleteMath,
981
+ isSameNode,
982
+ nodesWithProps,
983
+ removeAIMarks,
984
+ removeAINodes,
985
+ streamDeserializeInlineMd,
986
+ streamDeserializeMd,
987
+ streamInsertChunk,
988
+ streamSerializeMd,
989
+ undoAI,
990
+ withAIBatch
991
+ });
992
+ //# sourceMappingURL=index.js.map