@platejs/ai 50.1.2 → 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
  }
@@ -3884,25 +3976,160 @@ var getCurrentBlockPath = (editor) => {
3884
3976
  return path;
3885
3977
  };
3886
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
+
3887
4114
  // src/react/copilot/CopilotPlugin.tsx
3888
4115
  import { serializeMd } from "@platejs/markdown";
3889
4116
  import debounce from "lodash/debounce.js";
3890
4117
  import {
3891
4118
  bindFirst as bindFirst3,
3892
- KEYS as KEYS21,
4119
+ KEYS as KEYS22,
3893
4120
  NodeApi as NodeApi4
3894
4121
  } from "platejs";
3895
4122
  import { createTPlatePlugin as createTPlatePlugin2 } from "platejs/react";
3896
4123
 
3897
4124
  // src/react/copilot/renderCopilotBelowNodes.tsx
3898
4125
  import React from "react";
3899
- import { KEYS as KEYS17 } from "platejs";
4126
+ import { KEYS as KEYS18 } from "platejs";
3900
4127
  import { getEditorPlugin as getEditorPlugin4 } from "platejs/react";
3901
4128
  var renderCopilotBelowNodes = ({
3902
4129
  editor
3903
4130
  }) => {
3904
4131
  const copilot = getEditorPlugin4(editor, {
3905
- key: KEYS17.copilot
4132
+ key: KEYS18.copilot
3906
4133
  });
3907
4134
  const { renderGhostText: GhostText } = copilot.getOptions();
3908
4135
  if (!GhostText) return;
@@ -3913,10 +4140,10 @@ var renderCopilotBelowNodes = ({
3913
4140
 
3914
4141
  // src/react/copilot/transforms/acceptCopilot.ts
3915
4142
  import { deserializeInlineMd } from "@platejs/markdown";
3916
- import { KEYS as KEYS18 } from "platejs";
4143
+ import { KEYS as KEYS19 } from "platejs";
3917
4144
  var acceptCopilot = (editor) => {
3918
4145
  const { suggestionText } = editor.getOptions({
3919
- key: KEYS18.copilot
4146
+ key: KEYS19.copilot
3920
4147
  });
3921
4148
  if (!suggestionText?.length) return false;
3922
4149
  editor.tf.insertFragment(deserializeInlineMd(editor, suggestionText));
@@ -3924,7 +4151,7 @@ var acceptCopilot = (editor) => {
3924
4151
 
3925
4152
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
3926
4153
  import { deserializeInlineMd as deserializeInlineMd2 } from "@platejs/markdown";
3927
- import { KEYS as KEYS20 } from "platejs";
4154
+ import { KEYS as KEYS21 } from "platejs";
3928
4155
  import { getEditorPlugin as getEditorPlugin6 } from "platejs/react";
3929
4156
 
3930
4157
  // src/react/copilot/utils/callCompletionApi.ts
@@ -4042,17 +4269,17 @@ var getNextWord = ({ text }) => {
4042
4269
  };
4043
4270
 
4044
4271
  // src/react/copilot/utils/triggerCopilotSuggestion.ts
4045
- import { KEYS as KEYS19 } from "platejs";
4272
+ import { KEYS as KEYS20 } from "platejs";
4046
4273
  import { getEditorPlugin as getEditorPlugin5 } from "platejs/react";
4047
4274
  var triggerCopilotSuggestion = async (editor) => {
4048
4275
  const { api, getOptions, setOption } = getEditorPlugin5(
4049
4276
  editor,
4050
4277
  {
4051
- key: KEYS19.copilot
4278
+ key: KEYS20.copilot
4052
4279
  }
4053
4280
  );
4054
4281
  const { completeOptions, getPrompt, isLoading, triggerQuery } = getOptions();
4055
- if (isLoading || editor.getOptions({ key: KEYS19.aiChat }).chat?.isLoading) {
4282
+ if (isLoading || editor.getOptions({ key: KEYS20.aiChat }).chat?.isLoading) {
4056
4283
  return false;
4057
4284
  }
4058
4285
  if (!triggerQuery({ editor })) return false;
@@ -4086,7 +4313,7 @@ var withoutAbort = (editor, fn) => {
4086
4313
  // src/react/copilot/transforms/acceptCopilotNextWord.ts
4087
4314
  var acceptCopilotNextWord = (editor) => {
4088
4315
  const { api, getOptions } = getEditorPlugin6(editor, {
4089
- key: KEYS20.copilot
4316
+ key: KEYS21.copilot
4090
4317
  });
4091
4318
  const { getNextWord: getNextWord2, suggestionText } = getOptions();
4092
4319
  if (!suggestionText?.length) {
@@ -4202,7 +4429,7 @@ var withCopilot = ({
4202
4429
 
4203
4430
  // src/react/copilot/CopilotPlugin.tsx
4204
4431
  var CopilotPlugin = createTPlatePlugin2({
4205
- key: KEYS21.copilot,
4432
+ key: KEYS22.copilot,
4206
4433
  handlers: {
4207
4434
  onBlur: ({ api }) => {
4208
4435
  api.copilot.reject();
@@ -4224,7 +4451,7 @@ var CopilotPlugin = createTPlatePlugin2({
4224
4451
  suggestionNodeId: null,
4225
4452
  suggestionText: null,
4226
4453
  autoTriggerQuery: ({ editor }) => {
4227
- if (editor.getOptions({ key: KEYS21.copilot }).suggestionText) {
4454
+ if (editor.getOptions({ key: KEYS22.copilot }).suggestionText) {
4228
4455
  return false;
4229
4456
  }
4230
4457
  const isEmpty = editor.api.isEmpty(editor.selection, { block: true });
@@ -4314,7 +4541,9 @@ export {
4314
4541
  acceptAIChat,
4315
4542
  acceptCopilot,
4316
4543
  acceptCopilotNextWord,
4544
+ acceptSuggestions,
4317
4545
  aiCommentToRange,
4546
+ applyAISuggestions,
4318
4547
  callCompletionApi,
4319
4548
  createFormattedBlocks,
4320
4549
  escapeInput,
@@ -4325,6 +4554,7 @@ export {
4325
4554
  getListNode,
4326
4555
  getNextWord,
4327
4556
  insertBelowAIChat,
4557
+ insertBelowGenerate,
4328
4558
  isCompleteCodeBlock,
4329
4559
  isCompleteMath,
4330
4560
  isSameNode,