@platejs/ai 50.1.2 → 50.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,14 @@ 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);
3266
+ setOption("chatSelection", blocks.length > 0 ? null : editor.selection);
3189
3267
  const ctx = {
3190
3268
  children: editor.children,
3191
3269
  selection: selection ?? null,
@@ -3210,7 +3288,7 @@ var withAIChat = ({
3210
3288
  api,
3211
3289
  editor,
3212
3290
  getOptions,
3213
- tf: { insertText, normalizeNode, setSelection },
3291
+ tf: { apply, insertText, normalizeNode },
3214
3292
  type
3215
3293
  }) => {
3216
3294
  const tf = editor.getTransforms(AIPlugin);
@@ -3272,8 +3350,11 @@ var AIChatPlugin = createTPlatePlugin({
3272
3350
  _blockChunks: "",
3273
3351
  _blockPath: null,
3274
3352
  _mdxName: null,
3353
+ _replaceIds: [],
3275
3354
  aiEditor: null,
3276
3355
  chat: { messages: [] },
3356
+ chatNodes: [],
3357
+ chatSelection: null,
3277
3358
  experimental_lastTextId: null,
3278
3359
  mode: "insert",
3279
3360
  open: false,
@@ -3313,11 +3394,25 @@ var AIChatPlugin = createTPlatePlugin({
3313
3394
  });
3314
3395
  },
3315
3396
  reload: () => {
3316
- const { chat, mode } = getOptions();
3317
- if (mode === "insert") {
3318
- editor.getTransforms(AIPlugin).ai.undo();
3397
+ const { chat, chatNodes, chatSelection } = getOptions();
3398
+ editor.getTransforms(AIPlugin).ai.undo();
3399
+ if (chatSelection) {
3400
+ editor.tf.setSelection(chatSelection);
3401
+ } else {
3402
+ editor.getApi(BlockSelectionPlugin4).blockSelection.set(chatNodes.map((node) => node.id));
3319
3403
  }
3320
- void chat.regenerate?.();
3404
+ const blocks = editor.getApi(BlockSelectionPlugin4).blockSelection.getNodes();
3405
+ const selection = blocks.length > 0 ? editor.api.nodesRange(blocks) : editor.selection;
3406
+ const ctx = {
3407
+ children: editor.children,
3408
+ selection: selection ?? null,
3409
+ toolName: getOption("toolName")
3410
+ };
3411
+ void chat.regenerate?.({
3412
+ body: {
3413
+ ctx
3414
+ }
3415
+ });
3321
3416
  },
3322
3417
  stop: () => {
3323
3418
  setOption("streaming", false);
@@ -3325,12 +3420,15 @@ var AIChatPlugin = createTPlatePlugin({
3325
3420
  }
3326
3421
  };
3327
3422
  }).extendApi(({ api, editor, getOptions, setOption, tf, type }) => ({
3328
- hide: () => {
3423
+ hide: ({ focus = true } = {}) => {
3329
3424
  api.aiChat.reset();
3330
3425
  setOption("open", false);
3331
- if (editor.getOption(BlockSelectionPlugin4, "isSelectingSome")) {
3332
- } else {
3333
- editor.tf.focus();
3426
+ if (focus) {
3427
+ if (editor.getOption(BlockSelectionPlugin4, "isSelectingSome")) {
3428
+ editor.getApi(BlockSelectionPlugin4).blockSelection.focus();
3429
+ } else {
3430
+ editor.tf.focus();
3431
+ }
3334
3432
  }
3335
3433
  const lastBatch = editor.history.undos.at(-1);
3336
3434
  if (lastBatch?.ai) {
@@ -3340,6 +3438,7 @@ var AIChatPlugin = createTPlatePlugin({
3340
3438
  },
3341
3439
  show: () => {
3342
3440
  api.aiChat.reset();
3441
+ setOption("toolName", null);
3343
3442
  getOptions().chat.setMessages?.([]);
3344
3443
  setOption("open", true);
3345
3444
  }
@@ -3884,25 +3983,161 @@ var getCurrentBlockPath = (editor) => {
3884
3983
  return path;
3885
3984
  };
3886
3985
 
3986
+ // src/react/ai-chat/utils/applyAISuggestions.ts
3987
+ import { deserializeMd as deserializeMd2 } from "@platejs/markdown";
3988
+ import { BlockSelectionPlugin as BlockSelectionPlugin6 } from "@platejs/selection/react";
3989
+ import {
3990
+ diffToSuggestions,
3991
+ getTransientSuggestionKey as getTransientSuggestionKey3,
3992
+ SkipSuggestionDeletes
3993
+ } from "@platejs/suggestion";
3994
+ import {
3995
+ ElementApi as ElementApi6,
3996
+ KEYS as KEYS17,
3997
+ nanoid,
3998
+ TextApi as TextApi5
3999
+ } from "platejs";
4000
+ var applyAISuggestions = (editor, content) => {
4001
+ const isBlockSelecting = editor.getOption(
4002
+ BlockSelectionPlugin6,
4003
+ "isSelectingSome"
4004
+ );
4005
+ const { chatNodes } = editor.getOptions(AIChatPlugin);
4006
+ if (isBlockSelecting) {
4007
+ const setReplaceIds = (ids) => {
4008
+ editor.setOption(AIChatPlugin, "_replaceIds", ids);
4009
+ };
4010
+ if (editor.getOption(AIChatPlugin, "_replaceIds").length === 0) {
4011
+ setReplaceIds(chatNodes.map((node) => node.id));
4012
+ }
4013
+ const diffNodes = getDiffNodes(editor, content);
4014
+ const replaceNodes = Array.from(
4015
+ editor.api.nodes({
4016
+ at: [],
4017
+ match: (n) => ElementApi6.isElement(n) && editor.getOption(AIChatPlugin, "_replaceIds").includes(n.id)
4018
+ })
4019
+ );
4020
+ replaceNodes.forEach(([node, path], index) => {
4021
+ const replaceNode = node;
4022
+ const diffNode = diffNodes[index];
4023
+ const isSameString = SkipSuggestionDeletes(editor, replaceNode) === SkipSuggestionDeletes(editor, diffNode);
4024
+ const isSameSuggestion = replaceNode.suggestion?.type === diffNode.suggestion?.type;
4025
+ if (isSameString && isSameSuggestion && node.id == diffNode.id) {
4026
+ return;
4027
+ }
4028
+ if (index === replaceNodes.length - 1 && diffNodes.length > replaceNodes.length) {
4029
+ editor.tf.replaceNodes(diffNodes.slice(index), {
4030
+ at: path
4031
+ });
4032
+ } else {
4033
+ editor.tf.replaceNodes(diffNode, {
4034
+ at: path
4035
+ });
4036
+ }
4037
+ });
4038
+ editor.getApi(BlockSelectionPlugin6).blockSelection.set(diffNodes.map((node) => node.id));
4039
+ setReplaceIds(diffNodes.map((node) => node.id));
4040
+ } else {
4041
+ const diffNodes = getDiffNodes(editor, content);
4042
+ editor.tf.insertFragment(diffNodes);
4043
+ const nodes = editor.api.nodes({
4044
+ at: [],
4045
+ mode: "lowest",
4046
+ match: (n) => TextApi5.isText(n) && !!n[getTransientSuggestionKey3()]
4047
+ });
4048
+ const range = editor.api.nodesRange(Array.from(nodes));
4049
+ editor.tf.setSelection(range);
4050
+ return;
4051
+ }
4052
+ };
4053
+ var withProps = (diffNodes, chatNodes) => {
4054
+ return diffNodes.map((node, index) => {
4055
+ if (!ElementApi6.isElement(node)) return node;
4056
+ const originalNode = chatNodes?.[index];
4057
+ return {
4058
+ ...node,
4059
+ ...originalNode ?? { id: nanoid() },
4060
+ children: node.children
4061
+ };
4062
+ });
4063
+ };
4064
+ var withTransient = (diffNodes) => {
4065
+ return diffNodes.map((node) => {
4066
+ if (TextApi5.isText(node)) {
4067
+ return {
4068
+ ...node,
4069
+ [getTransientSuggestionKey3()]: true
4070
+ };
4071
+ } else {
4072
+ return {
4073
+ ...node,
4074
+ children: withTransient(node.children),
4075
+ [getTransientSuggestionKey3()]: true
4076
+ };
4077
+ }
4078
+ });
4079
+ };
4080
+ var withoutSuggestionAndComments = (nodes) => {
4081
+ return nodes.map((node) => {
4082
+ if (TextApi5.isText(node)) {
4083
+ if (node[KEYS17.suggestion] || node[KEYS17.comment]) {
4084
+ return {
4085
+ text: node.text
4086
+ };
4087
+ }
4088
+ return node;
4089
+ }
4090
+ if (ElementApi6.isElement(node)) {
4091
+ if (node[KEYS17.suggestion]) {
4092
+ const nodeWithoutSuggestion = {};
4093
+ Object.keys(node).forEach((key) => {
4094
+ if (key !== KEYS17.suggestion && !key.startsWith(KEYS17.suggestion)) {
4095
+ nodeWithoutSuggestion[key] = node[key];
4096
+ }
4097
+ });
4098
+ return {
4099
+ ...nodeWithoutSuggestion,
4100
+ children: withoutSuggestionAndComments(node.children)
4101
+ };
4102
+ }
4103
+ return {
4104
+ ...node,
4105
+ children: withoutSuggestionAndComments(node.children)
4106
+ };
4107
+ }
4108
+ return node;
4109
+ });
4110
+ };
4111
+ var getDiffNodes = (editor, aiContent) => {
4112
+ const chatNodes = withoutSuggestionAndComments(
4113
+ editor.getOption(AIChatPlugin, "chatNodes")
4114
+ );
4115
+ const aiNodes = withProps(deserializeMd2(editor, aiContent), chatNodes);
4116
+ const diffNodes = withTransient(
4117
+ diffToSuggestions(editor, chatNodes, aiNodes, { ignoreProps: ["id"] })
4118
+ );
4119
+ return diffNodes;
4120
+ };
4121
+
3887
4122
  // src/react/copilot/CopilotPlugin.tsx
3888
4123
  import { serializeMd } from "@platejs/markdown";
3889
4124
  import debounce from "lodash/debounce.js";
3890
4125
  import {
3891
4126
  bindFirst as bindFirst3,
3892
- KEYS as KEYS21,
4127
+ KEYS as KEYS22,
3893
4128
  NodeApi as NodeApi4
3894
4129
  } from "platejs";
3895
4130
  import { createTPlatePlugin as createTPlatePlugin2 } from "platejs/react";
3896
4131
 
3897
4132
  // src/react/copilot/renderCopilotBelowNodes.tsx
3898
4133
  import React from "react";
3899
- import { KEYS as KEYS17 } from "platejs";
4134
+ import { KEYS as KEYS18 } from "platejs";
3900
4135
  import { getEditorPlugin as getEditorPlugin4 } from "platejs/react";
3901
4136
  var renderCopilotBelowNodes = ({
3902
4137
  editor
3903
4138
  }) => {
3904
4139
  const copilot = getEditorPlugin4(editor, {
3905
- key: KEYS17.copilot
4140
+ key: KEYS18.copilot
3906
4141
  });
3907
4142
  const { renderGhostText: GhostText } = copilot.getOptions();
3908
4143
  if (!GhostText) return;
@@ -3913,10 +4148,10 @@ var renderCopilotBelowNodes = ({
3913
4148
 
3914
4149
  // src/react/copilot/transforms/acceptCopilot.ts
3915
4150
  import { deserializeInlineMd } from "@platejs/markdown";
3916
- import { KEYS as KEYS18 } from "platejs";
4151
+ import { KEYS as KEYS19 } from "platejs";
3917
4152
  var acceptCopilot = (editor) => {
3918
4153
  const { suggestionText } = editor.getOptions({
3919
- key: KEYS18.copilot
4154
+ key: KEYS19.copilot
3920
4155
  });
3921
4156
  if (!suggestionText?.length) return false;
3922
4157
  editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
@@ -3924,7 +4159,7 @@ var acceptCopilot = (editor) => {
3924
4159
 
3925
4160
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
3926
4161
  import { deserializeInlineMd as deserializeInlineMd2 } from "@platejs/markdown";
3927
- import { KEYS as KEYS20 } from "platejs";
4162
+ import { KEYS as KEYS21 } from "platejs";
3928
4163
  import { getEditorPlugin as getEditorPlugin6 } from "platejs/react";
3929
4164
 
3930
4165
  // src/react/copilot/utils/callCompletionApi.ts
@@ -4042,17 +4277,17 @@ var getNextWord = ({ text }) => {
4042
4277
  };
4043
4278
 
4044
4279
  // src/react/copilot/utils/triggerCopilotSuggestion.ts
4045
- import { KEYS as KEYS19 } from "platejs";
4280
+ import { KEYS as KEYS20 } from "platejs";
4046
4281
  import { getEditorPlugin as getEditorPlugin5 } from "platejs/react";
4047
4282
  var triggerCopilotSuggestion = async (editor) => {
4048
4283
  const { api, getOptions, setOption } = getEditorPlugin5(
4049
4284
  editor,
4050
4285
  {
4051
- key: KEYS19.copilot
4286
+ key: KEYS20.copilot
4052
4287
  }
4053
4288
  );
4054
4289
  const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
4055
- if (isLoading || editor.getOptions({ key: KEYS19.aiChat }).chat?.isLoading) {
4290
+ if (isLoading || editor.getOptions({ key: KEYS20.aiChat }).chat?.isLoading) {
4056
4291
  return false;
4057
4292
  }
4058
4293
  if (!triggerQuery({ editor })) return false;
@@ -4086,7 +4321,7 @@ var withoutAbort = (editor, fn) => {
4086
4321
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
4087
4322
  var acceptCopilotNextWord = (editor) => {
4088
4323
  const { api, getOptions } = getEditorPlugin6(editor, {
4089
- key: KEYS20.copilot
4324
+ key: KEYS21.copilot
4090
4325
  });
4091
4326
  const { getNextWord: getNextWord2, suggestionText } = getOptions();
4092
4327
  if (!suggestionText?.length) {
@@ -4202,7 +4437,7 @@ var withCopilot = ({
4202
4437
 
4203
4438
  // src/react/copilot/CopilotPlugin.tsx
4204
4439
  var CopilotPlugin = createTPlatePlugin2({
4205
- key: KEYS21.copilot,
4440
+ key: KEYS22.copilot,
4206
4441
  handlers: {
4207
4442
  onBlur: ({ api }) => {
4208
4443
  api.copilot.reject();
@@ -4224,7 +4459,7 @@ var CopilotPlugin = createTPlatePlugin2({
4224
4459
  suggestionNodeId: null,
4225
4460
  suggestionText: null,
4226
4461
  autoTriggerQuery: ({ editor }) => {
4227
- if (editor.getOptions({ key: KEYS21.copilot }).suggestionText) {
4462
+ if (editor.getOptions({ key: KEYS22.copilot }).suggestionText) {
4228
4463
  return false;
4229
4464
  }
4230
4465
  const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
@@ -4314,7 +4549,9 @@ export {
4314
4549
  acceptAIChat,
4315
4550
  acceptCopilot,
4316
4551
  acceptCopilotNextWord,
4552
+ acceptSuggestions,
4317
4553
  aiCommentToRange,
4554
+ applyAISuggestions,
4318
4555
  callCompletionApi,
4319
4556
  createFormattedBlocks,
4320
4557
  escapeInput,
@@ -4325,6 +4562,7 @@ export {
4325
4562
  getListNode,
4326
4563
  getNextWord,
4327
4564
  insertBelowAIChat,
4565
+ insertBelowGenerate,
4328
4566
  isCompleteCodeBlock,
4329
4567
  isCompleteMath,
4330
4568
  isSameNode,