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