@platejs/ai 50.1.1 → 50.2.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.
@@ -46,11 +46,16 @@ var removeAINodes = (editor, { at = [] } = {}) => {
46
46
  };
47
47
 
48
48
  // src/lib/transforms/undoAI.ts
49
+ import { getTransientSuggestionKey } from "@platejs/suggestion";
49
50
  var undoAI = (editor) => {
50
- if (editor.history.undos.at(-1)?.ai && editor.api.some({
51
+ const hasAINodeOrAISuggestion = editor.api.some({
51
52
  at: [],
52
53
  match: (n) => !!n.ai
53
- })) {
54
+ }) || editor.api.some({
55
+ at: [],
56
+ match: (n) => !!n[getTransientSuggestionKey()]
57
+ });
58
+ if (editor.history.undos.at(-1)?.ai && hasAINodeOrAISuggestion) {
54
59
  editor.undo();
55
60
  editor.history.redos.pop();
56
61
  }
@@ -2925,22 +2930,59 @@ import {
2925
2930
  import { createTPlatePlugin } from "platejs/react";
2926
2931
 
2927
2932
  // src/react/ai-chat/transforms/acceptAIChat.ts
2933
+ import {
2934
+ acceptSuggestion,
2935
+ getSuggestionKey,
2936
+ getTransientSuggestionKey as getTransientSuggestionKey2
2937
+ } from "@platejs/suggestion";
2938
+ import { SuggestionPlugin } from "@platejs/suggestion/react";
2928
2939
  import { KEYS as KEYS4 } from "platejs";
2929
2940
  import { getEditorPlugin } from "platejs/react";
2930
2941
  var acceptAIChat = (editor) => {
2931
- const { tf } = getEditorPlugin(editor, AIPlugin);
2932
- const api = editor.getApi({ key: KEYS4.ai });
2933
- const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
2934
- withAIBatch(editor, () => {
2935
- tf.ai.removeMarks();
2936
- editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
2942
+ const mode = editor.getOption(AIChatPlugin, "mode");
2943
+ if (mode === "insert") {
2944
+ const { tf } = getEditorPlugin(editor, AIPlugin);
2945
+ const api = editor.getApi({ key: KEYS4.ai });
2946
+ const lastAINodePath = api.aiChat.node({ at: [], reverse: true })[1];
2947
+ withAIBatch(editor, () => {
2948
+ tf.ai.removeMarks();
2949
+ editor.getTransforms(AIChatPlugin).aiChat.removeAnchor();
2950
+ });
2951
+ api.aiChat.hide();
2952
+ editor.tf.focus();
2953
+ const focusPoint = editor.api.end(lastAINodePath);
2954
+ editor.tf.setSelection({
2955
+ anchor: focusPoint,
2956
+ focus: focusPoint
2957
+ });
2958
+ }
2959
+ if (mode === "chat") {
2960
+ withAIBatch(editor, () => {
2961
+ acceptSuggestions(editor);
2962
+ });
2963
+ editor.getApi(AIChatPlugin).aiChat.hide();
2964
+ }
2965
+ };
2966
+ var acceptSuggestions = (editor) => {
2967
+ const suggestions = editor.getApi(SuggestionPlugin).suggestion.nodes({
2968
+ transient: true
2969
+ });
2970
+ suggestions.forEach(([suggestionNode]) => {
2971
+ const suggestionData = editor.getApi(SuggestionPlugin).suggestion.suggestionData(suggestionNode);
2972
+ if (!suggestionData) return;
2973
+ const description = {
2974
+ createdAt: new Date(suggestionData.createdAt),
2975
+ keyId: getSuggestionKey(suggestionData.id),
2976
+ suggestionId: suggestionData.id,
2977
+ type: suggestionData.type,
2978
+ userId: suggestionData.userId
2979
+ };
2980
+ acceptSuggestion(editor, description);
2937
2981
  });
2938
- api.aiChat.hide();
2939
- editor.tf.focus();
2940
- const focusPoint = editor.api.end(lastAINodePath);
2941
- editor.tf.setSelection({
2942
- anchor: focusPoint,
2943
- focus: focusPoint
2982
+ editor.tf.unsetNodes([getTransientSuggestionKey2()], {
2983
+ at: [],
2984
+ mode: "all",
2985
+ match: (n) => !!n[getTransientSuggestionKey2()]
2944
2986
  });
2945
2987
  };
2946
2988
 
@@ -3062,6 +3104,29 @@ var replaceSelectionAIChat = (editor, sourceEditor, { format = "single" } = {})
3062
3104
 
3063
3105
  // src/react/ai-chat/transforms/insertBelowAIChat.ts
3064
3106
  var insertBelowAIChat = (editor, sourceEditor, { format = "single" } = {}) => {
3107
+ const { toolName } = editor.getOptions(AIChatPlugin);
3108
+ if (toolName === "generate")
3109
+ return insertBelowGenerate(editor, sourceEditor, { format });
3110
+ const selectedBlocks = editor.getApi(BlockSelectionPlugin2).blockSelection.getNodes();
3111
+ const selectedIds = editor.getOptions(BlockSelectionPlugin2).selectedIds;
3112
+ editor.getTransforms(AIPlugin).ai.undo();
3113
+ const insertBlocksAndSelect = editor.getTransforms(BlockSelectionPlugin2).blockSelection.insertBlocksAndSelect;
3114
+ if (!selectedIds || selectedIds.size === 0) return;
3115
+ const lastBlock = selectedBlocks.at(-1);
3116
+ if (!lastBlock) return;
3117
+ const nextPath = PathApi.next(lastBlock[1]);
3118
+ const nodes = selectedBlocks.map((block) => block[0]);
3119
+ insertBlocksAndSelect(nodes, {
3120
+ at: nextPath,
3121
+ insertedCallback: () => {
3122
+ withAIBatch(editor, () => {
3123
+ acceptSuggestions(editor);
3124
+ });
3125
+ }
3126
+ });
3127
+ editor.getApi(AIChatPlugin).aiChat.hide({ focus: false });
3128
+ };
3129
+ var insertBelowGenerate = (editor, sourceEditor, { format = "single" } = {}) => {
3065
3130
  if (!sourceEditor || sourceEditor.api.isEmpty()) return;
3066
3131
  const isBlockSelecting = editor.getOption(
3067
3132
  BlockSelectionPlugin2,
@@ -3138,14 +3203,23 @@ var removeAnchorAIChat = (editor, options) => {
3138
3203
  import { KEYS as KEYS8 } from "platejs";
3139
3204
  import { getEditorPlugin as getEditorPlugin2 } from "platejs/react";
3140
3205
  var resetAIChat = (editor) => {
3141
- const { api, getOptions } = getEditorPlugin2(editor, {
3142
- key: KEYS8.aiChat
3143
- });
3206
+ const { api, getOptions, setOptions } = getEditorPlugin2(
3207
+ editor,
3208
+ {
3209
+ key: KEYS8.aiChat
3210
+ }
3211
+ );
3144
3212
  api.aiChat.stop();
3145
3213
  const chat = getOptions().chat;
3146
3214
  if (chat.messages && chat.messages.length > 0) {
3147
3215
  chat.setMessages?.([]);
3148
3216
  }
3217
+ setOptions({
3218
+ _replaceIds: [],
3219
+ chatNodes: [],
3220
+ mode: "insert",
3221
+ toolName: "generate"
3222
+ });
3149
3223
  editor.getTransforms(AIPlugin).ai.undo();
3150
3224
  };
3151
3225
 
@@ -3182,10 +3256,13 @@ var submitAIChat = (editor, input, {
3182
3256
  setOption("mode", mode);
3183
3257
  setOption("toolName", toolName ?? null);
3184
3258
  const blocks = editor.getApi(BlockSelectionPlugin3).blockSelection.getNodes();
3259
+ const blocksRange = editor.api.nodesRange(blocks);
3185
3260
  const promptText = getEditorPrompt(editor, {
3186
3261
  prompt
3187
3262
  });
3188
- const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
3263
+ const selection = blocks.length > 0 ? blocksRange : editor.selection;
3264
+ const chatNodes = blocks.length > 0 ? blocks.map((block) => block[0]) : editor.api.fragment();
3265
+ setOption("chatNodes", chatNodes);
3189
3266
  const ctx = {
3190
3267
  children: editor.children,
3191
3268
  selection: selection ?? null,
@@ -3210,7 +3287,7 @@ var withAIChat = ({
3210
3287
  api,
3211
3288
  editor,
3212
3289
  getOptions,
3213
- tf: { insertText, normalizeNode, setSelection },
3290
+ tf: { apply, insertText, normalizeNode },
3214
3291
  type
3215
3292
  }) => {
3216
3293
  const tf = editor.getTransforms(AIPlugin);
@@ -3272,8 +3349,10 @@ var AIChatPlugin = createTPlatePlugin({
3272
3349
  _blockChunks: "",
3273
3350
  _blockPath: null,
3274
3351
  _mdxName: null,
3352
+ _replaceIds: [],
3275
3353
  aiEditor: null,
3276
3354
  chat: { messages: [] },
3355
+ chatNodes: [],
3277
3356
  experimental_lastTextId: null,
3278
3357
  mode: "insert",
3279
3358
  open: false,
@@ -3313,11 +3392,20 @@ var AIChatPlugin = createTPlatePlugin({
3313
3392
  });
3314
3393
  },
3315
3394
  reload: () => {
3316
- const { chat, mode } = getOptions();
3317
- if (mode === "insert") {
3318
- editor.getTransforms(AIPlugin).ai.undo();
3319
- }
3320
- void chat.regenerate?.();
3395
+ const { chat } = getOptions();
3396
+ editor.getTransforms(AIPlugin).ai.undo();
3397
+ const blocks = editor.getApi(BlockSelectionPlugin4).blockSelection.getNodes();
3398
+ const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
3399
+ const ctx = {
3400
+ children: editor.children,
3401
+ selection: selection ?? null,
3402
+ toolName: getOption("toolName")
3403
+ };
3404
+ void chat.regenerate?.({
3405
+ body: {
3406
+ ctx
3407
+ }
3408
+ });
3321
3409
  },
3322
3410
  stop: () => {
3323
3411
  setOption("streaming", false);
@@ -3325,12 +3413,15 @@ var AIChatPlugin = createTPlatePlugin({
3325
3413
  }
3326
3414
  };
3327
3415
  }).extendApi(({ api, editor, getOptions, setOption, tf, type }) => ({
3328
- hide: () => {
3416
+ hide: ({ focus = true } = {}) => {
3329
3417
  api.aiChat.reset();
3330
3418
  setOption("open", false);
3331
- if (editor.getOption(BlockSelectionPlugin4, "isSelectingSome")) {
3332
- } else {
3333
- editor.tf.focus();
3419
+ if (focus) {
3420
+ if (editor.getOption(BlockSelectionPlugin4, "isSelectingSome")) {
3421
+ editor.getApi(BlockSelectionPlugin4).blockSelection.focus();
3422
+ } else {
3423
+ editor.tf.focus();
3424
+ }
3334
3425
  }
3335
3426
  const lastBatch = editor.history.undos.at(-1);
3336
3427
  if (lastBatch?.ai) {
@@ -3340,6 +3431,7 @@ var AIChatPlugin = createTPlatePlugin({
3340
3431
  },
3341
3432
  show: () => {
3342
3433
  api.aiChat.reset();
3434
+ setOption("toolName", null);
3343
3435
  getOptions().chat.setMessages?.([]);
3344
3436
  setOption("open", true);
3345
3437
  }
@@ -3697,7 +3789,6 @@ var streamSerializeMd = (editor, options, chunk) => {
3697
3789
  const { value } = trimEndHeading(editor, optionsValue ?? editor.children);
3698
3790
  let result = "";
3699
3791
  result = editor.getApi(MarkdownPlugin4).markdown.serialize({
3700
- preserveEmptyParagraphs: false,
3701
3792
  value,
3702
3793
  ...restOptions
3703
3794
  });
@@ -3885,25 +3976,160 @@ var getCurrentBlockPath = (editor) => {
3885
3976
  return path;
3886
3977
  };
3887
3978
 
3979
+ // src/react/ai-chat/utils/applyAISuggestions.ts
3980
+ import { deserializeMd as deserializeMd2 } from "@platejs/markdown";
3981
+ import { BlockSelectionPlugin as BlockSelectionPlugin6 } from "@platejs/selection/react";
3982
+ import {
3983
+ diffToSuggestions,
3984
+ getTransientSuggestionKey as getTransientSuggestionKey3,
3985
+ SkipSuggestionDeletes
3986
+ } from "@platejs/suggestion";
3987
+ import {
3988
+ ElementApi as ElementApi6,
3989
+ KEYS as KEYS17,
3990
+ nanoid,
3991
+ TextApi as TextApi5
3992
+ } from "platejs";
3993
+ var applyAISuggestions = (editor, content) => {
3994
+ const isBlockSelecting = editor.getOption(
3995
+ BlockSelectionPlugin6,
3996
+ "isSelectingSome"
3997
+ );
3998
+ const { chatNodes } = editor.getOptions(AIChatPlugin);
3999
+ if (isBlockSelecting) {
4000
+ const setReplaceIds = (ids) => {
4001
+ editor.setOption(AIChatPlugin, "_replaceIds", ids);
4002
+ };
4003
+ if (editor.getOption(AIChatPlugin, "_replaceIds").length === 0) {
4004
+ setReplaceIds(chatNodes.map((node) => node.id));
4005
+ }
4006
+ const diffNodes = getDiffNodes(editor, content);
4007
+ const replaceNodes = Array.from(
4008
+ editor.api.nodes({
4009
+ at: [],
4010
+ match: (n) => ElementApi6.isElement(n) && editor.getOption(AIChatPlugin, "_replaceIds").includes(n.id)
4011
+ })
4012
+ );
4013
+ replaceNodes.forEach(([node, path], index) => {
4014
+ const replaceNode = node;
4015
+ const diffNode = diffNodes[index];
4016
+ const isSameString = SkipSuggestionDeletes(editor, replaceNode) === SkipSuggestionDeletes(editor, diffNode);
4017
+ const isSameSuggestion = replaceNode.suggestion?.type === diffNode.suggestion?.type;
4018
+ if (isSameString && isSameSuggestion && node.id == diffNode.id) {
4019
+ return;
4020
+ }
4021
+ if (index === replaceNodes.length - 1 && diffNodes.length > replaceNodes.length) {
4022
+ editor.tf.replaceNodes(diffNodes.slice(index), {
4023
+ at: path
4024
+ });
4025
+ } else {
4026
+ editor.tf.replaceNodes(diffNode, {
4027
+ at: path
4028
+ });
4029
+ }
4030
+ });
4031
+ setReplaceIds(diffNodes.map((node) => node.id));
4032
+ } else {
4033
+ const diffNodes = getDiffNodes(editor, content);
4034
+ editor.tf.insertFragment(diffNodes);
4035
+ const nodes = editor.api.nodes({
4036
+ at: [],
4037
+ mode: "lowest",
4038
+ match: (n) => TextApi5.isText(n) && !!n[getTransientSuggestionKey3()]
4039
+ });
4040
+ const range = editor.api.nodesRange(Array.from(nodes));
4041
+ editor.tf.setSelection(range);
4042
+ return;
4043
+ }
4044
+ };
4045
+ var withProps = (diffNodes, chatNodes) => {
4046
+ return diffNodes.map((node, index) => {
4047
+ if (!ElementApi6.isElement(node)) return node;
4048
+ const originalNode = chatNodes?.[index];
4049
+ return {
4050
+ ...node,
4051
+ ...originalNode ?? { id: nanoid() },
4052
+ children: node.children
4053
+ };
4054
+ });
4055
+ };
4056
+ var withTransient = (diffNodes) => {
4057
+ return diffNodes.map((node) => {
4058
+ if (TextApi5.isText(node)) {
4059
+ return {
4060
+ ...node,
4061
+ [getTransientSuggestionKey3()]: true
4062
+ };
4063
+ } else {
4064
+ return {
4065
+ ...node,
4066
+ children: withTransient(node.children),
4067
+ [getTransientSuggestionKey3()]: true
4068
+ };
4069
+ }
4070
+ });
4071
+ };
4072
+ var withoutSuggestionAndComments = (nodes) => {
4073
+ return nodes.map((node) => {
4074
+ if (TextApi5.isText(node)) {
4075
+ if (node[KEYS17.suggestion] || node[KEYS17.comment]) {
4076
+ return {
4077
+ text: node.text
4078
+ };
4079
+ }
4080
+ return node;
4081
+ }
4082
+ if (ElementApi6.isElement(node)) {
4083
+ if (node[KEYS17.suggestion]) {
4084
+ const nodeWithoutSuggestion = {};
4085
+ Object.keys(node).forEach((key) => {
4086
+ if (key !== KEYS17.suggestion && !key.startsWith(KEYS17.suggestion)) {
4087
+ nodeWithoutSuggestion[key] = node[key];
4088
+ }
4089
+ });
4090
+ return {
4091
+ ...nodeWithoutSuggestion,
4092
+ children: withoutSuggestionAndComments(node.children)
4093
+ };
4094
+ }
4095
+ return {
4096
+ ...node,
4097
+ children: withoutSuggestionAndComments(node.children)
4098
+ };
4099
+ }
4100
+ return node;
4101
+ });
4102
+ };
4103
+ var getDiffNodes = (editor, aiContent) => {
4104
+ const chatNodes = withoutSuggestionAndComments(
4105
+ editor.getOption(AIChatPlugin, "chatNodes")
4106
+ );
4107
+ const aiNodes = withProps(deserializeMd2(editor, aiContent), chatNodes);
4108
+ const diffNodes = withTransient(
4109
+ diffToSuggestions(editor, chatNodes, aiNodes, { ignoreProps: ["id"] })
4110
+ );
4111
+ return diffNodes;
4112
+ };
4113
+
3888
4114
  // src/react/copilot/CopilotPlugin.tsx
3889
4115
  import { serializeMd } from "@platejs/markdown";
3890
4116
  import debounce from "lodash/debounce.js";
3891
4117
  import {
3892
4118
  bindFirst as bindFirst3,
3893
- KEYS as KEYS21,
4119
+ KEYS as KEYS22,
3894
4120
  NodeApi as NodeApi4
3895
4121
  } from "platejs";
3896
4122
  import { createTPlatePlugin as createTPlatePlugin2 } from "platejs/react";
3897
4123
 
3898
4124
  // src/react/copilot/renderCopilotBelowNodes.tsx
3899
4125
  import React from "react";
3900
- import { KEYS as KEYS17 } from "platejs";
4126
+ import { KEYS as KEYS18 } from "platejs";
3901
4127
  import { getEditorPlugin as getEditorPlugin4 } from "platejs/react";
3902
4128
  var renderCopilotBelowNodes = ({
3903
4129
  editor
3904
4130
  }) => {
3905
4131
  const copilot = getEditorPlugin4(editor, {
3906
- key: KEYS17.copilot
4132
+ key: KEYS18.copilot
3907
4133
  });
3908
4134
  const { renderGhostText: GhostText } = copilot.getOptions();
3909
4135
  if (!GhostText) return;
@@ -3914,10 +4140,10 @@ var renderCopilotBelowNodes = ({
3914
4140
 
3915
4141
  // src/react/copilot/transforms/acceptCopilot.ts
3916
4142
  import { deserializeInlineMd } from "@platejs/markdown";
3917
- import { KEYS as KEYS18 } from "platejs";
4143
+ import { KEYS as KEYS19 } from "platejs";
3918
4144
  var acceptCopilot = (editor) => {
3919
4145
  const { suggestionText } = editor.getOptions({
3920
- key: KEYS18.copilot
4146
+ key: KEYS19.copilot
3921
4147
  });
3922
4148
  if (!suggestionText?.length) return false;
3923
4149
  editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
@@ -3925,7 +4151,7 @@ var acceptCopilot = (editor) => {
3925
4151
 
3926
4152
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
3927
4153
  import { deserializeInlineMd as deserializeInlineMd2 } from "@platejs/markdown";
3928
- import { KEYS as KEYS20 } from "platejs";
4154
+ import { KEYS as KEYS21 } from "platejs";
3929
4155
  import { getEditorPlugin as getEditorPlugin6 } from "platejs/react";
3930
4156
 
3931
4157
  // src/react/copilot/utils/callCompletionApi.ts
@@ -4043,17 +4269,17 @@ var getNextWord = ({ text }) => {
4043
4269
  };
4044
4270
 
4045
4271
  // src/react/copilot/utils/triggerCopilotSuggestion.ts
4046
- import { KEYS as KEYS19 } from "platejs";
4272
+ import { KEYS as KEYS20 } from "platejs";
4047
4273
  import { getEditorPlugin as getEditorPlugin5 } from "platejs/react";
4048
4274
  var triggerCopilotSuggestion = async (editor) => {
4049
4275
  const { api, getOptions, setOption } = getEditorPlugin5(
4050
4276
  editor,
4051
4277
  {
4052
- key: KEYS19.copilot
4278
+ key: KEYS20.copilot
4053
4279
  }
4054
4280
  );
4055
4281
  const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
4056
- if (isLoading || editor.getOptions({ key: KEYS19.aiChat }).chat?.isLoading) {
4282
+ if (isLoading || editor.getOptions({ key: KEYS20.aiChat }).chat?.isLoading) {
4057
4283
  return false;
4058
4284
  }
4059
4285
  if (!triggerQuery({ editor })) return false;
@@ -4087,7 +4313,7 @@ var withoutAbort = (editor, fn) => {
4087
4313
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
4088
4314
  var acceptCopilotNextWord = (editor) => {
4089
4315
  const { api, getOptions } = getEditorPlugin6(editor, {
4090
- key: KEYS20.copilot
4316
+ key: KEYS21.copilot
4091
4317
  });
4092
4318
  const { getNextWord: getNextWord2, suggestionText } = getOptions();
4093
4319
  if (!suggestionText?.length) {
@@ -4203,7 +4429,7 @@ var withCopilot = ({
4203
4429
 
4204
4430
  // src/react/copilot/CopilotPlugin.tsx
4205
4431
  var CopilotPlugin = createTPlatePlugin2({
4206
- key: KEYS21.copilot,
4432
+ key: KEYS22.copilot,
4207
4433
  handlers: {
4208
4434
  onBlur: ({ api }) => {
4209
4435
  api.copilot.reject();
@@ -4225,7 +4451,7 @@ var CopilotPlugin = createTPlatePlugin2({
4225
4451
  suggestionNodeId: null,
4226
4452
  suggestionText: null,
4227
4453
  autoTriggerQuery: ({ editor }) => {
4228
- if (editor.getOptions({ key: KEYS21.copilot }).suggestionText) {
4454
+ if (editor.getOptions({ key: KEYS22.copilot }).suggestionText) {
4229
4455
  return false;
4230
4456
  }
4231
4457
  const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
@@ -4315,7 +4541,9 @@ export {
4315
4541
  acceptAIChat,
4316
4542
  acceptCopilot,
4317
4543
  acceptCopilotNextWord,
4544
+ acceptSuggestions,
4318
4545
  aiCommentToRange,
4546
+ applyAISuggestions,
4319
4547
  callCompletionApi,
4320
4548
  createFormattedBlocks,
4321
4549
  escapeInput,
@@ -4326,6 +4554,7 @@ export {
4326
4554
  getListNode,
4327
4555
  getNextWord,
4328
4556
  insertBelowAIChat,
4557
+ insertBelowGenerate,
4329
4558
  isCompleteCodeBlock,
4330
4559
  isCompleteMath,
4331
4560
  isSameNode,