@lofcz/platejs-ai 52.3.2 → 52.3.6

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.
@@ -0,0 +1,235 @@
1
+ import { ElementApi, KEYS, TextApi, bindFirst, createTSlatePlugin, getPluginType } from "platejs";
2
+ import cloneDeep from "lodash/cloneDeep.js";
3
+ import { getTransientSuggestionKey } from "@platejs/suggestion";
4
+ import { isSelecting } from "@platejs/selection";
5
+
6
+ //#region src/lib/transforms/aiStreamSnapshot.ts
7
+ const AI_PREVIEW_KEY = "aiPreview";
8
+ const AI_STREAM_SNAPSHOT = /* @__PURE__ */ new WeakMap();
9
+ const clearAIPreview = (editor) => {
10
+ AI_STREAM_SNAPSHOT.delete(editor);
11
+ };
12
+ const getAIPreview = (editor) => AI_STREAM_SNAPSHOT.get(editor);
13
+ const getAIPreviewRange = (editor) => {
14
+ let closed = false;
15
+ let end = -1;
16
+ let invalid = false;
17
+ let start = -1;
18
+ editor.children.forEach((node, index) => {
19
+ if (!node?.[AI_PREVIEW_KEY]) {
20
+ if (start !== -1) closed = true;
21
+ return;
22
+ }
23
+ if (closed) {
24
+ invalid = true;
25
+ return;
26
+ }
27
+ if (start === -1) start = index;
28
+ end = index;
29
+ });
30
+ if (invalid) return { kind: "invalid" };
31
+ if (start === -1 && end === -1) return { kind: "none" };
32
+ return {
33
+ end,
34
+ kind: "range",
35
+ start
36
+ };
37
+ };
38
+ const removeAIPreviewAnchor = (editor) => {
39
+ const aiChatType = getPluginType(editor, KEYS.aiChat);
40
+ editor.tf.removeNodes({
41
+ at: [],
42
+ match: (node) => ElementApi.isElement(node) && node.type === aiChatType
43
+ });
44
+ };
45
+ const restoreAIPreviewSelection = (editor, selection) => {
46
+ if (selection) {
47
+ editor.tf.select(cloneDeep(selection));
48
+ return;
49
+ }
50
+ editor.tf.deselect();
51
+ };
52
+ const removePreviewRange = (editor, range) => {
53
+ for (let index = range.end; index >= range.start; index--) editor.tf.removeNodes({ at: [index] });
54
+ };
55
+ const replacePreviewRange = (editor, range, blocks) => {
56
+ removePreviewRange(editor, range);
57
+ if (blocks.length === 0) return;
58
+ editor.tf.insertNodes(cloneDeep(blocks), { at: [range.start] });
59
+ };
60
+ const cloneAcceptedPreviewBlocks = (editor, range) => {
61
+ const aiType = getPluginType(editor, KEYS.ai);
62
+ const blocks = cloneDeep(editor.children.slice(range.start, range.end + 1));
63
+ const stripNode = (node) => {
64
+ if (ElementApi.isElement(node)) {
65
+ const { [AI_PREVIEW_KEY]: _preview, children, ...rest$1 } = node;
66
+ return {
67
+ ...rest$1,
68
+ children: children.map(stripNode)
69
+ };
70
+ }
71
+ const { [aiType]: _ai, ...rest } = node;
72
+ return rest;
73
+ };
74
+ return blocks.map(stripNode);
75
+ };
76
+ const beginAIPreview = (editor, { originalBlocks = [] } = {}) => {
77
+ if (getAIPreview(editor)) return false;
78
+ AI_STREAM_SNAPSHOT.set(editor, {
79
+ originalBlocks: cloneDeep(originalBlocks),
80
+ selectionBefore: cloneDeep(editor.selection)
81
+ });
82
+ return true;
83
+ };
84
+ const hasAIPreview = (editor) => !!getAIPreview(editor);
85
+ const cancelAIPreview = (editor) => {
86
+ const preview = getAIPreview(editor);
87
+ if (!preview) return false;
88
+ const range = getAIPreviewRange(editor);
89
+ if (range.kind === "invalid") return false;
90
+ editor.tf.withoutSaving(() => {
91
+ if (range.kind === "range") replacePreviewRange(editor, range, preview.originalBlocks);
92
+ removeAIPreviewAnchor(editor);
93
+ restoreAIPreviewSelection(editor, preview.selectionBefore);
94
+ });
95
+ clearAIPreview(editor);
96
+ return true;
97
+ };
98
+ const discardAIPreview = (editor) => {
99
+ if (!getAIPreview(editor)) return false;
100
+ clearAIPreview(editor);
101
+ return true;
102
+ };
103
+ const acceptAIPreview = (editor, _value) => {
104
+ const preview = getAIPreview(editor);
105
+ if (!preview) return false;
106
+ const range = getAIPreviewRange(editor);
107
+ if (range.kind === "invalid") return false;
108
+ if (range.kind === "range") {
109
+ const acceptedBlocks = cloneAcceptedPreviewBlocks(editor, range);
110
+ editor.tf.withoutSaving(() => {
111
+ replacePreviewRange(editor, range, preview.originalBlocks);
112
+ removeAIPreviewAnchor(editor);
113
+ restoreAIPreviewSelection(editor, preview.selectionBefore);
114
+ });
115
+ editor.tf.withNewBatch(() => {
116
+ if (preview.originalBlocks.length > 0) for (let index = preview.originalBlocks.length - 1; index >= 0; index--) editor.tf.removeNodes({ at: [range.start + index] });
117
+ if (acceptedBlocks.length > 0) editor.tf.insertNodes(acceptedBlocks, { at: [range.start] });
118
+ });
119
+ const lastBatch = editor.history?.undos.at(-1);
120
+ if (lastBatch) lastBatch.selectionBefore = cloneDeep(preview.selectionBefore);
121
+ } else editor.tf.withoutSaving(() => {
122
+ removeAIPreviewAnchor(editor);
123
+ });
124
+ clearAIPreview(editor);
125
+ return true;
126
+ };
127
+
128
+ //#endregion
129
+ //#region src/lib/transforms/insertAINodes.ts
130
+ const insertAINodes = (editor, nodes, { target } = {}) => {
131
+ if (!target && !editor.selection?.focus.path) return;
132
+ const aiNodes = nodes.map((node) => ({
133
+ ...node,
134
+ ai: true
135
+ }));
136
+ editor.tf.withoutNormalizing(() => {
137
+ editor.tf.insertNodes(aiNodes, {
138
+ at: editor.api.end(target || editor.selection.focus.path),
139
+ select: true
140
+ });
141
+ editor.tf.collapse({ edge: "end" });
142
+ });
143
+ };
144
+
145
+ //#endregion
146
+ //#region src/lib/transforms/removeAIMarks.ts
147
+ const removeAIMarks = (editor, { at = [] } = {}) => {
148
+ const nodeType = getPluginType(editor, KEYS.ai);
149
+ editor.tf.unsetNodes(nodeType, {
150
+ at,
151
+ match: (n) => n[nodeType]
152
+ });
153
+ };
154
+
155
+ //#endregion
156
+ //#region src/lib/transforms/removeAINodes.ts
157
+ const removeAINodes = (editor, { at = [] } = {}) => {
158
+ editor.tf.removeNodes({
159
+ at,
160
+ match: (n) => TextApi.isText(n) && !!n.ai
161
+ });
162
+ };
163
+
164
+ //#endregion
165
+ //#region src/lib/transforms/undoAI.ts
166
+ const undoAI = (editor) => {
167
+ if (hasAIPreview(editor) && cancelAIPreview(editor)) return;
168
+ const hasAINodeOrAISuggestion = editor.api.some({
169
+ at: [],
170
+ match: (n) => !!n.ai
171
+ }) || editor.api.some({
172
+ at: [],
173
+ match: (n) => !!n[getTransientSuggestionKey()]
174
+ });
175
+ if (editor.history.undos.at(-1)?.ai && hasAINodeOrAISuggestion) {
176
+ editor.undo();
177
+ editor.history.redos.pop();
178
+ return;
179
+ }
180
+ if (hasAINodeOrAISuggestion) cancelAIPreview(editor);
181
+ };
182
+
183
+ //#endregion
184
+ //#region src/lib/transforms/withAIBatch.ts
185
+ const withAIBatch = (editor, fn, { split } = {}) => {
186
+ if (split) editor.tf.withNewBatch(fn);
187
+ else editor.tf.withMerging(fn);
188
+ const lastBatch = editor.history.undos?.at(-1);
189
+ if (lastBatch) lastBatch.ai = true;
190
+ };
191
+
192
+ //#endregion
193
+ //#region src/lib/BaseAIPlugin.ts
194
+ const getAITransforms = (editor) => ({
195
+ acceptPreview: bindFirst(acceptAIPreview, editor),
196
+ beginPreview: bindFirst(beginAIPreview, editor),
197
+ cancelPreview: bindFirst(cancelAIPreview, editor),
198
+ discardPreview: bindFirst(discardAIPreview, editor),
199
+ hasPreview: bindFirst(hasAIPreview, editor),
200
+ insertNodes: bindFirst(insertAINodes, editor),
201
+ removeMarks: bindFirst(removeAIMarks, editor),
202
+ removeNodes: bindFirst(removeAINodes, editor),
203
+ undo: bindFirst(undoAI, editor)
204
+ });
205
+ const BaseAIPlugin = createTSlatePlugin({
206
+ key: KEYS.ai,
207
+ node: {
208
+ isDecoration: false,
209
+ isLeaf: true
210
+ }
211
+ }).extendTransforms(({ editor }) => getAITransforms(editor)).extendEditorTransforms(({ editor }) => ({ ai: getAITransforms(editor) }));
212
+
213
+ //#endregion
214
+ //#region src/lib/utils/getEditorPrompt.ts
215
+ const createPromptFromConfig = (config, params) => {
216
+ const { isBlockSelecting, isSelecting: isSelecting$1 } = params;
217
+ if (isBlockSelecting && config.blockSelecting) return config.blockSelecting ?? config.default;
218
+ if (isSelecting$1 && config.selecting) return config.selecting ?? config.default;
219
+ return config.default;
220
+ };
221
+ const getEditorPrompt = (editor, { prompt = "" }) => {
222
+ const params = {
223
+ editor,
224
+ isBlockSelecting: editor.getOption({ key: KEYS.blockSelection }, "isSelectingSome"),
225
+ isSelecting: isSelecting(editor)
226
+ };
227
+ let promptText = "";
228
+ if (typeof prompt === "function") promptText = prompt(params);
229
+ else if (typeof prompt === "object") promptText = createPromptFromConfig(prompt, params);
230
+ else promptText = prompt;
231
+ return promptText;
232
+ };
233
+
234
+ //#endregion
235
+ export { removeAINodes as a, AI_PREVIEW_KEY as c, cancelAIPreview as d, discardAIPreview as f, undoAI as i, acceptAIPreview as l, BaseAIPlugin as n, removeAIMarks as o, hasAIPreview as p, withAIBatch as r, insertAINodes as s, getEditorPrompt as t, beginAIPreview as u };
@@ -0,0 +1,137 @@
1
+ import * as platejs0 from "platejs";
2
+ import { Descendant, History, OmitFirst, Path, PluginConfig, SlateEditor, TLocation, Value } from "platejs";
3
+
4
+ //#region src/lib/transforms/aiStreamSnapshot.d.ts
5
+ type BeginAIPreviewOptions = {
6
+ originalBlocks?: Value;
7
+ };
8
+ declare const AI_PREVIEW_KEY = "aiPreview";
9
+ declare const beginAIPreview: (editor: SlateEditor, {
10
+ originalBlocks
11
+ }?: BeginAIPreviewOptions) => boolean;
12
+ declare const hasAIPreview: (editor: SlateEditor) => boolean;
13
+ declare const cancelAIPreview: (editor: SlateEditor) => boolean;
14
+ declare const discardAIPreview: (editor: SlateEditor) => boolean;
15
+ declare const acceptAIPreview: (editor: SlateEditor, _value?: Value) => boolean;
16
+ //#endregion
17
+ //#region src/lib/transforms/insertAINodes.d.ts
18
+ declare const insertAINodes: (editor: SlateEditor, nodes: Descendant[], {
19
+ target
20
+ }?: {
21
+ target?: Path;
22
+ }) => void;
23
+ //#endregion
24
+ //#region src/lib/transforms/removeAIMarks.d.ts
25
+ declare const removeAIMarks: (editor: SlateEditor, {
26
+ at
27
+ }?: {
28
+ at?: TLocation;
29
+ }) => void;
30
+ //#endregion
31
+ //#region src/lib/transforms/removeAINodes.d.ts
32
+ declare const removeAINodes: (editor: SlateEditor, {
33
+ at
34
+ }?: {
35
+ at?: Path;
36
+ }) => void;
37
+ //#endregion
38
+ //#region src/lib/transforms/undoAI.d.ts
39
+ declare const undoAI: (editor: SlateEditor) => void;
40
+ //#endregion
41
+ //#region src/lib/transforms/withAIBatch.d.ts
42
+ type AIBatch = History['undos'][number] & {
43
+ ai?: boolean;
44
+ };
45
+ declare const withAIBatch: (editor: SlateEditor, fn: () => void, {
46
+ split
47
+ }?: {
48
+ split?: boolean;
49
+ }) => void;
50
+ //#endregion
51
+ //#region src/lib/BaseAIPlugin.d.ts
52
+ type BaseAIPluginConfig = PluginConfig<'ai', {}, {}, {
53
+ ai: {
54
+ /** Commit the active preview as one fresh undoable batch. */
55
+ acceptPreview: OmitFirst<typeof acceptAIPreview>;
56
+ /** Capture the rollback slice and selection for AI preview. */
57
+ beginPreview: OmitFirst<typeof beginAIPreview>;
58
+ /** Restore the rollback point and clear active preview state. */
59
+ cancelPreview: OmitFirst<typeof cancelAIPreview>;
60
+ /** Clear active preview bookkeeping without restoring content. */
61
+ discardPreview: OmitFirst<typeof discardAIPreview>;
62
+ /** Report whether an AI preview rollback point is active. */
63
+ hasPreview: OmitFirst<typeof hasAIPreview>;
64
+ insertNodes: OmitFirst<typeof insertAINodes>;
65
+ removeMarks: OmitFirst<typeof removeAIMarks>;
66
+ removeNodes: OmitFirst<typeof removeAINodes>;
67
+ undo: OmitFirst<typeof undoAI>;
68
+ };
69
+ }>;
70
+ declare const BaseAIPlugin: platejs0.SlatePlugin<PluginConfig<"ai", {}, {}, {
71
+ ai: {
72
+ acceptPreview: ((_value?: platejs0.Value | undefined) => boolean) & ((_value?: platejs0.Value | undefined) => boolean);
73
+ beginPreview: ((args_0?: {
74
+ originalBlocks?: platejs0.Value;
75
+ } | undefined) => boolean) & ((args_0?: {
76
+ originalBlocks?: platejs0.Value;
77
+ } | undefined) => boolean);
78
+ cancelPreview: (() => boolean) & (() => boolean);
79
+ discardPreview: (() => boolean) & (() => boolean);
80
+ hasPreview: (() => boolean) & (() => boolean);
81
+ insertNodes: ((nodes: platejs0.Descendant[], args_1?: {
82
+ target?: platejs0.Path;
83
+ } | undefined) => void) & ((nodes: platejs0.Descendant[], args_1?: {
84
+ target?: platejs0.Path;
85
+ } | undefined) => void);
86
+ removeMarks: ((args_0?: {
87
+ at?: platejs0.TLocation;
88
+ } | undefined) => void) & ((args_0?: {
89
+ at?: platejs0.TLocation;
90
+ } | undefined) => void);
91
+ removeNodes: ((args_0?: {
92
+ at?: platejs0.Path;
93
+ } | undefined) => void) & ((args_0?: {
94
+ at?: platejs0.Path;
95
+ } | undefined) => void);
96
+ undo: (() => void) & (() => void);
97
+ };
98
+ }, {}>>;
99
+ //#endregion
100
+ //#region src/lib/types.d.ts
101
+ type AIToolName = 'comment' | 'edit' | 'generate' | null;
102
+ type AIMode = 'chat' | 'insert';
103
+ //#endregion
104
+ //#region src/lib/utils/getEditorPrompt.d.ts
105
+ type EditorPrompt = ((params: EditorPromptParams) => string) | PromptConfig | string;
106
+ type EditorPromptParams = {
107
+ editor: SlateEditor;
108
+ isBlockSelecting: boolean;
109
+ isSelecting: boolean;
110
+ };
111
+ type PromptConfig = {
112
+ default: string;
113
+ blockSelecting?: string;
114
+ selecting?: string;
115
+ };
116
+ declare const getEditorPrompt: (editor: SlateEditor, {
117
+ prompt
118
+ }: {
119
+ prompt?: EditorPrompt;
120
+ }) => string;
121
+ //#endregion
122
+ //#region src/lib/utils/replacePlaceholders.d.ts
123
+ type MarkdownType = 'block' | 'blockSelection' | 'blockSelectionWithBlockId' | 'blockWithBlockId' | 'editor' | 'editorWithBlockId' | 'tableCellWithId';
124
+ declare const replacePlaceholders: (editor: SlateEditor, text: string, {
125
+ prompt
126
+ }?: {
127
+ prompt?: string;
128
+ }) => string;
129
+ //#endregion
130
+ //#region src/lib/utils/getMarkdown.d.ts
131
+ declare const getMarkdown: (editor: SlateEditor, {
132
+ type
133
+ }: {
134
+ type: MarkdownType;
135
+ }) => string;
136
+ //#endregion
137
+ export { hasAIPreview as C, discardAIPreview as S, insertAINodes as _, EditorPromptParams as a, beginAIPreview as b, AIMode as c, BaseAIPluginConfig as d, AIBatch as f, removeAIMarks as g, removeAINodes as h, EditorPrompt as i, AIToolName as l, undoAI as m, MarkdownType as n, PromptConfig as o, withAIBatch as p, replacePlaceholders as r, getEditorPrompt as s, getMarkdown as t, BaseAIPlugin as u, AI_PREVIEW_KEY as v, cancelAIPreview as x, acceptAIPreview as y };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as insertAINodes, a as EditorPromptParams, c as AIMode, d as BaseAIPluginConfig, f as AIBatch, g as removeAIMarks, h as removeAINodes, i as EditorPrompt, l as AIToolName, m as undoAI, n as MarkdownType, o as PromptConfig, p as withAIBatch, r as replacePlaceholders, s as getEditorPrompt, t as getMarkdown, u as BaseAIPlugin } from "./index-B_bqJoKL";
2
- export { AIBatch, AIMode, AIToolName, BaseAIPlugin, BaseAIPluginConfig, EditorPrompt, EditorPromptParams, MarkdownType, PromptConfig, getEditorPrompt, getMarkdown, insertAINodes, removeAIMarks, removeAINodes, replacePlaceholders, undoAI, withAIBatch };
1
+ import { C as hasAIPreview, S as discardAIPreview, _ as insertAINodes, a as EditorPromptParams, b as beginAIPreview, c as AIMode, d as BaseAIPluginConfig, f as AIBatch, g as removeAIMarks, h as removeAINodes, i as EditorPrompt, l as AIToolName, m as undoAI, n as MarkdownType, o as PromptConfig, p as withAIBatch, r as replacePlaceholders, s as getEditorPrompt, t as getMarkdown, u as BaseAIPlugin, v as AI_PREVIEW_KEY, x as cancelAIPreview, y as acceptAIPreview } from "./index-D2hJDomP";
2
+ export { AIBatch, AIMode, AIToolName, AI_PREVIEW_KEY, BaseAIPlugin, BaseAIPluginConfig, EditorPrompt, EditorPromptParams, MarkdownType, PromptConfig, acceptAIPreview, beginAIPreview, cancelAIPreview, discardAIPreview, getEditorPrompt, getMarkdown, hasAIPreview, insertAINodes, removeAIMarks, removeAINodes, replacePlaceholders, undoAI, withAIBatch };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as removeAINodes, i as undoAI, n as BaseAIPlugin, o as removeAIMarks, r as withAIBatch, s as insertAINodes, t as getEditorPrompt } from "./getEditorPrompt-BnVrgUl3.js";
1
+ import { a as removeAINodes, c as AI_PREVIEW_KEY, d as cancelAIPreview, f as discardAIPreview, i as undoAI, l as acceptAIPreview, n as BaseAIPlugin, o as removeAIMarks, p as hasAIPreview, r as withAIBatch, s as insertAINodes, t as getEditorPrompt, u as beginAIPreview } from "./getEditorPrompt-axDm9Fg4.js";
2
2
  import { KEYS } from "platejs";
3
3
  import { serializeMd } from "@platejs/markdown";
4
4
  import { getTableGridAbove } from "@platejs/table";
@@ -105,7 +105,7 @@ const getMarkdown = (editor, { type }) => {
105
105
  //#endregion
106
106
  //#region src/lib/utils/replacePlaceholders.ts
107
107
  const replacePlaceholders = (editor, text, { prompt } = {}) => {
108
- let result = text.replace("{prompt}", prompt || "");
108
+ let result = text.split("{prompt}").join(prompt || "");
109
109
  Object.entries({
110
110
  "{blockSelectionWithBlockId}": "blockSelectionWithBlockId",
111
111
  "{blockSelection}": "blockSelection",
@@ -115,11 +115,10 @@ const replacePlaceholders = (editor, text, { prompt } = {}) => {
115
115
  "{editor}": "editor",
116
116
  "{tableCellWithId}": "tableCellWithId"
117
117
  }).forEach(([placeholder, type]) => {
118
- if (result.includes(placeholder)) result = result.replace(placeholder, getMarkdown(editor, { type }));
118
+ if (result.includes(placeholder)) result = result.split(placeholder).join(getMarkdown(editor, { type }));
119
119
  });
120
120
  return result;
121
121
  };
122
122
 
123
123
  //#endregion
124
- export { BaseAIPlugin, getEditorPrompt, getMarkdown, insertAINodes, removeAIMarks, removeAINodes, replacePlaceholders, undoAI, withAIBatch };
125
- //# sourceMappingURL=index.js.map
124
+ export { AI_PREVIEW_KEY, BaseAIPlugin, acceptAIPreview, beginAIPreview, cancelAIPreview, discardAIPreview, getEditorPrompt, getMarkdown, hasAIPreview, insertAINodes, removeAIMarks, removeAINodes, replacePlaceholders, undoAI, withAIBatch };