@@ -42,39 +42,10 @@ type AutoCompleteSuggestion = {
4242 suggestion : string ;
4343} ;
4444
45- async function fetchAutoCompleteSuggestions (
46- state : EditorState ,
47- _signal : AbortSignal ,
48- ) {
49- // TODO: options to get block json until selection
50- const text = state . doc . textBetween (
51- state . selection . from - 300 ,
52- state . selection . from ,
53- ) ;
54-
55- const response = await fetch (
56- `https://localhost:3000/ai/autocomplete/generateText` ,
57- {
58- method : "POST" ,
59- body : JSON . stringify ( { text } ) ,
60- } ,
61- ) ;
62- const data = await response . json ( ) ;
63- return data . suggestions . map ( ( suggestion : string ) => ( {
64- position : state . selection . from ,
65- suggestion : suggestion ,
66- } ) ) ;
67- // return [
68- // {
69- // position: state.selection.from,
70- // suggestion: "Hello World",
71- // },
72- // {
73- // position: state.selection.from,
74- // suggestion: "Hello Planet",
75- // },
76- // ];
77- }
45+ type AutoCompleteProvider = (
46+ editor : BlockNoteEditor < any , any , any > ,
47+ signal : AbortSignal ,
48+ ) => Promise < AutoCompleteSuggestion [ ] > ;
7849
7950function getMatchingSuggestions (
8051 autoCompleteSuggestions : AutoCompleteSuggestion [ ] ,
@@ -131,10 +102,10 @@ export class AutoCompleteProseMirrorPlugin<
131102 private autoCompleteSuggestions : AutoCompleteSuggestion [ ] = [ ] ;
132103
133104 private debounceFetchSuggestions = debounceWithAbort (
134- async ( state : EditorState , signal : AbortSignal ) => {
105+ async ( editor : BlockNoteEditor < any , any , any > , signal : AbortSignal ) => {
135106 // fetch suggestions
136- const autoCompleteSuggestions = await fetchAutoCompleteSuggestions (
137- state ,
107+ const autoCompleteSuggestions = await this . options . autoCompleteProvider (
108+ editor ,
138109 signal ,
139110 ) ;
140111
@@ -155,7 +126,9 @@ export class AutoCompleteProseMirrorPlugin<
155126
156127 constructor (
157128 private readonly editor : BlockNoteEditor < BSchema , I , S > ,
158- options : { } ,
129+ private readonly options : {
130+ autoCompleteProvider : AutoCompleteProvider ;
131+ } ,
159132 ) {
160133 super ( ) ;
161134
@@ -179,7 +152,7 @@ export class AutoCompleteProseMirrorPlugin<
179152 // Apply changes to the plugin state from an editor transaction.
180153 apply : (
181154 transaction ,
182- prev ,
155+ _prev ,
183156 _oldState ,
184157 newState ,
185158 ) : AutoCompleteState => {
@@ -204,96 +177,19 @@ export class AutoCompleteProseMirrorPlugin<
204177
205178 // No matching suggestions, if isUserInput is true, debounce fetch suggestions
206179 if ( transaction . getMeta ( autoCompletePluginKey ) ?. isUserInput ) {
207- this . debounceFetchSuggestions ( newState ) . catch ( ( error ) => {
208- /* eslint-disable-next-line no-console */
209- console . error ( error ) ;
180+ // TODO: this queueMicrotask is a workaround to ensure the transaction is applied before the debounceFetchSuggestions is called
181+ // (discuss with Nick what ideal architecture would be)
182+ queueMicrotask ( ( ) => {
183+ this . debounceFetchSuggestions ( self . editor ) . catch ( ( error ) => {
184+ /* eslint-disable-next-line no-console */
185+ console . error ( error ) ;
186+ } ) ;
210187 } ) ;
211188 } else {
212189 // clear suggestions
213190 this . autoCompleteSuggestions = [ ] ;
214191 }
215192 return undefined ;
216-
217- // Ignore transactions in code blocks.
218- // if (transaction.selection.$from.parent.type.spec.code) {
219- // return prev;
220- // }
221-
222- // // Either contains the trigger character if the menu should be shown,
223- // // or null if it should be hidden.
224- // const suggestionPluginTransactionMeta: {
225- // triggerCharacter: string;
226- // deleteTriggerCharacter?: boolean;
227- // ignoreQueryLength?: boolean;
228- // } | null = transaction.getMeta(autoCompletePluginKey);
229-
230- // if (
231- // typeof suggestionPluginTransactionMeta === "object" &&
232- // suggestionPluginTransactionMeta !== null
233- // ) {
234- // if (prev) {
235- // // Close the previous menu if it exists
236- // this.closeMenu();
237- // }
238- // const trackedPosition = trackPosition(
239- // editor,
240- // newState.selection.from -
241- // // Need to account for the trigger char that was inserted, so we offset the position by the length of the trigger character.
242- // suggestionPluginTransactionMeta.triggerCharacter.length,
243- // );
244- // return {
245- // triggerCharacter:
246- // suggestionPluginTransactionMeta.triggerCharacter,
247- // deleteTriggerCharacter:
248- // suggestionPluginTransactionMeta.deleteTriggerCharacter !==
249- // false,
250- // // When reading the queryStartPos, we offset the result by the length of the trigger character, to make it easy on the caller
251- // queryStartPos: () =>
252- // trackedPosition() +
253- // suggestionPluginTransactionMeta.triggerCharacter.length,
254- // query: "",
255- // decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
256- // ignoreQueryLength:
257- // suggestionPluginTransactionMeta?.ignoreQueryLength,
258- // };
259- // }
260-
261- // // Checks if the menu is hidden, in which case it doesn't need to be hidden or updated.
262- // if (prev === undefined) {
263- // return prev;
264- // }
265-
266- // // Checks if the menu should be hidden.
267- // if (
268- // // Highlighting text should hide the menu.
269- // newState.selection.from !== newState.selection.to ||
270- // // Transactions with plugin metadata should hide the menu.
271- // suggestionPluginTransactionMeta === null ||
272- // // Certain mouse events should hide the menu.
273- // // TODO: Change to global mousedown listener.
274- // transaction.getMeta("focus") ||
275- // transaction.getMeta("blur") ||
276- // transaction.getMeta("pointer") ||
277- // // Moving the caret before the character which triggered the menu should hide it.
278- // (prev.triggerCharacter !== undefined &&
279- // newState.selection.from < prev.queryStartPos()) ||
280- // // Moving the caret to a new block should hide the menu.
281- // !newState.selection.$from.sameParent(
282- // newState.doc.resolve(prev.queryStartPos()),
283- // )
284- // ) {
285- // return undefined;
286- // }
287-
288- // const next = { ...prev };
289- // // here we wi
290- // // Updates the current query.
291- // next.query = newState.doc.textBetween(
292- // prev.queryStartPos(),
293- // newState.selection.from,
294- // );
295-
296- // return next;
297193 } ,
298194 } ,
299195
@@ -352,7 +248,7 @@ export class AutoCompleteProseMirrorPlugin<
352248 return null ;
353249 }
354250
355- console . log ( autoCompleteState ) ;
251+ // console.log(autoCompleteState);
356252 // Creates an inline decoration around the trigger character.
357253 return DecorationSet . create ( state . doc , [
358254 Decoration . widget (
@@ -432,11 +328,6 @@ export interface DebouncedFunction<T extends any[], R> {
432328 cancel ( ) : void ;
433329}
434330
435- // TODO: more to blocknote API?
436- // TODO: test with Collaboration edits
437- // TODO: compare kilocode / cline etc
438- // TODO: think about advanced scenarios (e.g.: multiple suggestions, etc.)
439- // TODO: double tap -> extra long
440331/**
441332 * Create a new AIExtension instance, this can be passed to the BlockNote editor via the `extensions` option
442333 */
@@ -456,3 +347,9 @@ export function getAIAutoCompleteExtension(
456347) {
457348 return editor . extension ( AutoCompleteProseMirrorPlugin ) ;
458349}
350+
351+ // TODO: move more to blocknote API?
352+ // TODO: test with Collaboration edits
353+ // TODO: compare kilocode / cline etc
354+ // TODO: think about advanced scenarios (e.g.: multiple suggestions, etc.)
355+ // TODO: double tap -> insert extra long suggestion
0 commit comments