@atlaskit/editor-plugin-autocomplete 2.4.0 → 2.5.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.
- package/CHANGELOG.md +39 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/analytics/ufo.js +111 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +126 -64
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +452 -0
- package/dist/cjs/pm-plugins/slow-lane-client.js +45 -16
- package/dist/cjs/pm-plugins/text-predictor.js +72 -40
- package/dist/es2019/analytics/ufo.js +110 -0
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +132 -72
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +361 -0
- package/dist/es2019/pm-plugins/slow-lane-client.js +29 -2
- package/dist/es2019/pm-plugins/text-predictor.js +48 -15
- package/dist/esm/analytics/ufo.js +105 -0
- package/dist/esm/pm-plugins/autocomplete-plugin.js +126 -64
- package/dist/esm/pm-plugins/local-slow-lane-client.js +443 -0
- package/dist/esm/pm-plugins/slow-lane-client.js +45 -16
- package/dist/esm/pm-plugins/text-predictor.js +73 -40
- package/dist/types/analytics/ufo.d.ts +38 -0
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +5 -0
- package/dist/types/pm-plugins/local-slow-lane-client.d.ts +102 -0
- package/dist/types-ts4.5/analytics/ufo.d.ts +38 -0
- package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +5 -0
- package/dist/types-ts4.5/pm-plugins/local-slow-lane-client.d.ts +102 -0
- package/package.json +7 -2
- package/src/analytics/ufo.ts +132 -0
- package/src/pm-plugins/autocomplete-plugin.ts +125 -64
- package/src/pm-plugins/local-slow-lane-client.ts +487 -0
- package/src/pm-plugins/slow-lane-client.ts +28 -2
- package/src/pm-plugins/text-predictor.ts +42 -12
- package/tsconfig.app.json +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
2
3
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
4
|
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
4
5
|
import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
|
|
@@ -14,6 +15,7 @@ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
|
14
15
|
import type { AutocompletePlugin } from '../autocompletePluginType';
|
|
15
16
|
|
|
16
17
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
18
|
+
import { createLocalSlowLaneClient, type LocalSlowLaneClient } from './local-slow-lane-client';
|
|
17
19
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
18
20
|
import {
|
|
19
21
|
predict,
|
|
@@ -23,12 +25,14 @@ import {
|
|
|
23
25
|
ingestDocumentPage,
|
|
24
26
|
} from './text-predictor';
|
|
25
27
|
|
|
26
|
-
const SLOW_LANE_ENDPOINT = '/gateway/api/v1/autocomplete/typeahead-encodings';
|
|
27
|
-
|
|
28
28
|
export const autocompletePluginKey: PluginKey = new PluginKey('autocomplete');
|
|
29
29
|
|
|
30
30
|
const DEBOUNCE_MS = 150;
|
|
31
31
|
|
|
32
|
+
const hasDestroy = (
|
|
33
|
+
client: ReturnType<typeof createSlowLaneClient> | LocalSlowLaneClient,
|
|
34
|
+
): client is LocalSlowLaneClient => 'destroy' in client && typeof client.destroy === 'function';
|
|
35
|
+
|
|
32
36
|
export interface AutocompletePluginState {
|
|
33
37
|
/** The decoration set containing the ghost text widget */
|
|
34
38
|
decorationSet: DecorationSet;
|
|
@@ -97,14 +101,18 @@ const setAutocompleteMeta = (
|
|
|
97
101
|
let lastShownGhostText = '';
|
|
98
102
|
|
|
99
103
|
const showGhostText = (view: EditorView, text: string, position: number): void => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
try {
|
|
105
|
+
const { state, dispatch } = view;
|
|
106
|
+
const decorationSet = createGhostTextDecorationSet(state, position, text);
|
|
107
|
+
const tr = setAutocompleteMeta(state.tr, {
|
|
108
|
+
ghostText: text,
|
|
109
|
+
ghostPosition: position,
|
|
110
|
+
decorationSet,
|
|
111
|
+
});
|
|
112
|
+
dispatch(tr);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/showGhostText' });
|
|
115
|
+
}
|
|
108
116
|
};
|
|
109
117
|
|
|
110
118
|
/**
|
|
@@ -117,12 +125,17 @@ const clearGhostText = (state: EditorState, dispatch?: (tr: Transaction) => void
|
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
if (dispatch) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
try {
|
|
129
|
+
const tr = setAutocompleteMeta(state.tr, {
|
|
130
|
+
ghostText: '',
|
|
131
|
+
ghostPosition: -1,
|
|
132
|
+
decorationSet: DecorationSet.empty,
|
|
133
|
+
});
|
|
134
|
+
dispatch(tr);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/clearGhostText' });
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
126
139
|
}
|
|
127
140
|
return true;
|
|
128
141
|
};
|
|
@@ -137,14 +150,19 @@ const acceptGhostText = (state: EditorState, dispatch?: (tr: Transaction) => voi
|
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
if (dispatch) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
try {
|
|
154
|
+
const { ghostText, ghostPosition } = pluginState;
|
|
155
|
+
let tr = state.tr.insertText(ghostText, ghostPosition);
|
|
156
|
+
tr = setAutocompleteMeta(tr, {
|
|
157
|
+
ghostText: '',
|
|
158
|
+
ghostPosition: -1,
|
|
159
|
+
decorationSet: DecorationSet.empty,
|
|
160
|
+
});
|
|
161
|
+
dispatch(tr);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/acceptGhostText' });
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
148
166
|
}
|
|
149
167
|
return true;
|
|
150
168
|
};
|
|
@@ -178,6 +196,11 @@ export interface AutocompletePluginOptions {
|
|
|
178
196
|
* Use this to serve vectors from a CDN or media service in production.
|
|
179
197
|
*/
|
|
180
198
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
199
|
+
/**
|
|
200
|
+
* When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
|
|
201
|
+
* the network-based slow-lane backend. Defaults to false (network client).
|
|
202
|
+
*/
|
|
203
|
+
useLocalModel?: boolean;
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
/**
|
|
@@ -235,6 +258,15 @@ export const createAutocompletePlugin = (
|
|
|
235
258
|
let lastSuggestionTypedLength = 0;
|
|
236
259
|
let lastSuggestionLength = 0;
|
|
237
260
|
|
|
261
|
+
const fireSuggestionDismissedAnalytics = (reason: 'escape' | 'blur'): void => {
|
|
262
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
263
|
+
action: ACTION.SUGGESTION_DISMISSED,
|
|
264
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
265
|
+
eventType: EVENT_TYPE.TRACK,
|
|
266
|
+
attributes: { reason },
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
238
270
|
const fireSuggestionInsertedAnalytics = (ghostText: string): void => {
|
|
239
271
|
const typedLength = lastSuggestionTypedLength;
|
|
240
272
|
const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
|
|
@@ -252,10 +284,14 @@ export const createAutocompletePlugin = (
|
|
|
252
284
|
});
|
|
253
285
|
};
|
|
254
286
|
|
|
255
|
-
const slowLaneClient =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
287
|
+
const slowLaneClient = options?.useLocalModel
|
|
288
|
+
? createLocalSlowLaneClient({
|
|
289
|
+
debounceMs: 300,
|
|
290
|
+
})
|
|
291
|
+
: createSlowLaneClient({
|
|
292
|
+
baseUrl: '',
|
|
293
|
+
debounceMs: 300,
|
|
294
|
+
});
|
|
259
295
|
setDefaultSlowLaneClient(slowLaneClient);
|
|
260
296
|
|
|
261
297
|
/**
|
|
@@ -269,45 +305,51 @@ export const createAutocompletePlugin = (
|
|
|
269
305
|
}
|
|
270
306
|
|
|
271
307
|
debounceTimer = setTimeout(() => {
|
|
272
|
-
|
|
273
|
-
|
|
308
|
+
try {
|
|
309
|
+
const { state } = view;
|
|
310
|
+
const { selection } = state;
|
|
274
311
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
// Only predict for cursor selections (not range selections)
|
|
313
|
+
if (!selection.empty) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
279
316
|
|
|
280
|
-
|
|
317
|
+
const textBefore = getTextBeforeCursor(state);
|
|
281
318
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
319
|
+
// Suppress re-showing the same suggestion the user just dismissed.
|
|
320
|
+
// Once the text context changes (user types or deletes), this clears automatically.
|
|
321
|
+
if (textBefore === dismissedContext) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
dismissedContext = null;
|
|
288
325
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
326
|
+
// Don't predict if there's not enough context
|
|
327
|
+
if (textBefore.trim().length < 3) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
293
330
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
331
|
+
// Tier 1 prediction is synchronous -- no async needed
|
|
332
|
+
const prediction = predict(textBefore);
|
|
333
|
+
|
|
334
|
+
if (prediction && prediction.length > 0) {
|
|
335
|
+
const typedLength = getTypedLengthForPrediction(textBefore);
|
|
336
|
+
lastSuggestionTypedLength = typedLength;
|
|
337
|
+
lastSuggestionLength = typedLength + prediction.length;
|
|
338
|
+
|
|
339
|
+
showGhostText(view, prediction, selection.from);
|
|
340
|
+
if (prediction !== lastShownGhostText) {
|
|
341
|
+
lastShownGhostText = prediction;
|
|
342
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
343
|
+
action: ACTION.SUGGESTION_VIEWED,
|
|
344
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
345
|
+
eventType: EVENT_TYPE.TRACK,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
310
348
|
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
logException(error as Error, {
|
|
351
|
+
location: 'editor-plugin-autocomplete/schedulePrediction',
|
|
352
|
+
});
|
|
311
353
|
}
|
|
312
354
|
}, DEBOUNCE_MS);
|
|
313
355
|
};
|
|
@@ -424,6 +466,7 @@ export const createAutocompletePlugin = (
|
|
|
424
466
|
const didClear = clearGhostText(state, dispatch);
|
|
425
467
|
if (didClear) {
|
|
426
468
|
dismissedContext = getTextBeforeCursor(state);
|
|
469
|
+
fireSuggestionDismissedAnalytics('escape');
|
|
427
470
|
}
|
|
428
471
|
return didClear;
|
|
429
472
|
},
|
|
@@ -436,12 +479,23 @@ export const createAutocompletePlugin = (
|
|
|
436
479
|
| undefined;
|
|
437
480
|
if (pluginState?.ghostText) {
|
|
438
481
|
clearGhostText(view.state, view.dispatch);
|
|
482
|
+
fireSuggestionDismissedAnalytics('blur');
|
|
439
483
|
}
|
|
440
484
|
return false;
|
|
441
485
|
},
|
|
442
486
|
focus: () => {
|
|
443
|
-
|
|
444
|
-
|
|
487
|
+
try {
|
|
488
|
+
loadDefaultVocabulary();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
logException(error as Error, {
|
|
491
|
+
location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
loadVectorsAsync({ getBinaryUrl: options?.getVectorsBinaryUrl }).catch((error) => {
|
|
495
|
+
logException(error as Error, {
|
|
496
|
+
location: 'editor-plugin-autocomplete/loadVectorsAsync',
|
|
497
|
+
});
|
|
498
|
+
});
|
|
445
499
|
if (!hasIngestedPage) {
|
|
446
500
|
hasIngestedPage = true;
|
|
447
501
|
if (options?.getContext) {
|
|
@@ -461,7 +515,11 @@ export const createAutocompletePlugin = (
|
|
|
461
515
|
}
|
|
462
516
|
context.siblingCommentsContents?.forEach(ingestDocumentPage);
|
|
463
517
|
})
|
|
464
|
-
.catch(() => {
|
|
518
|
+
.catch((error) => {
|
|
519
|
+
logException(error as Error, {
|
|
520
|
+
location: 'editor-plugin-autocomplete/getContext',
|
|
521
|
+
});
|
|
522
|
+
});
|
|
465
523
|
}
|
|
466
524
|
}
|
|
467
525
|
return false;
|
|
@@ -503,6 +561,9 @@ export const createAutocompletePlugin = (
|
|
|
503
561
|
if (debounceTimer) {
|
|
504
562
|
clearTimeout(debounceTimer);
|
|
505
563
|
}
|
|
564
|
+
if (hasDestroy(slowLaneClient)) {
|
|
565
|
+
slowLaneClient.destroy();
|
|
566
|
+
}
|
|
506
567
|
setDefaultSlowLaneClient(null);
|
|
507
568
|
},
|
|
508
569
|
}),
|