@atlaskit/editor-plugin-autocomplete 2.3.0 → 2.5.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.
- package/CHANGELOG.md +42 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/autocompletePlugin/package.json +15 -0
- package/autocompletePluginType/package.json +15 -0
- package/dist/cjs/analytics/ufo.js +111 -0
- package/dist/cjs/entry-points/autocompletePlugin.js +12 -0
- package/dist/cjs/entry-points/autocompletePluginType.js +1 -0
- package/dist/cjs/entry-points/src-pm-plugins-autocomplete-plugin.js +18 -0
- package/dist/cjs/entry-points/src-pm-plugins-slow-lane-client.js +36 -0
- package/dist/cjs/entry-points/src-pm-plugins-text-predictor.js +66 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +126 -64
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +447 -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/entry-points/autocompletePlugin.js +2 -0
- package/dist/es2019/entry-points/autocompletePluginType.js +0 -0
- package/dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js +2 -0
- package/dist/es2019/entry-points/src-pm-plugins-slow-lane-client.js +2 -0
- package/dist/es2019/entry-points/src-pm-plugins-text-predictor.js +2 -0
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +132 -72
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +355 -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/entry-points/autocompletePlugin.js +2 -0
- package/dist/esm/entry-points/autocompletePluginType.js +0 -0
- package/dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js +2 -0
- package/dist/esm/entry-points/src-pm-plugins-slow-lane-client.js +2 -0
- package/dist/esm/entry-points/src-pm-plugins-text-predictor.js +2 -0
- package/dist/esm/pm-plugins/autocomplete-plugin.js +126 -64
- package/dist/esm/pm-plugins/local-slow-lane-client.js +439 -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/entry-points/autocompletePlugin.d.ts +1 -0
- package/dist/types/entry-points/autocompletePluginType.d.ts +1 -0
- package/dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +2 -0
- package/dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts +2 -0
- package/dist/types/entry-points/src-pm-plugins-text-predictor.d.ts +2 -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/entry-points/autocompletePlugin.d.ts +1 -0
- package/dist/types-ts4.5/entry-points/autocompletePluginType.d.ts +1 -0
- package/dist/types-ts4.5/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +2 -0
- package/dist/types-ts4.5/entry-points/src-pm-plugins-slow-lane-client.d.ts +2 -0
- package/dist/types-ts4.5/entry-points/src-pm-plugins-text-predictor.d.ts +2 -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 +8 -3
- package/src/analytics/ufo.ts +139 -0
- package/src/entry-points/autocompletePlugin.tsx +2 -0
- package/src/entry-points/autocompletePluginType.ts +2 -0
- package/src/entry-points/src-pm-plugins-autocomplete-plugin.ts +7 -0
- package/src/entry-points/src-pm-plugins-slow-lane-client.ts +13 -0
- package/src/entry-points/src-pm-plugins-text-predictor.ts +14 -0
- package/src/pm-plugins/autocomplete-plugin/package.json +5 -5
- package/src/pm-plugins/autocomplete-plugin.ts +126 -64
- package/src/pm-plugins/local-slow-lane-client.ts +480 -0
- package/src/pm-plugins/slow-lane-client/package.json +5 -5
- package/src/pm-plugins/slow-lane-client.ts +28 -2
- package/src/pm-plugins/text-predictor/package.json +5 -5
- package/src/pm-plugins/text-predictor.ts +42 -12
- package/tsconfig.app.json +3 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UFO experience tracking helpers for `@atlaskit/editor-plugin-autocomplete`.
|
|
3
|
+
*
|
|
4
|
+
* Pattern follows `packages/linking-platform/smart-card/src/state/analytics/ufoExperiences.ts`
|
|
5
|
+
* (ConcurrentExperience keyed per request/instance) plus a try/catch safety wrap
|
|
6
|
+
* inspired by `packages/editor/collab-provider/src/analytics/ufo.ts` so a UFO
|
|
7
|
+
* runtime error can never break the autocomplete feature.
|
|
8
|
+
*
|
|
9
|
+
* Experience names must be registered in DataPortal's `task` attribute before
|
|
10
|
+
* they can power FE Reliability SLOs:
|
|
11
|
+
* https://hello.atlassian.net/wiki/spaces/AA6/pages/3961393753
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ConcurrentExperience,
|
|
16
|
+
type CustomData,
|
|
17
|
+
ExperiencePerformanceTypes,
|
|
18
|
+
ExperienceTypes,
|
|
19
|
+
} from '@atlaskit/ufo';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Experience name strings are surfaced downstream by the UFO pipeline as
|
|
23
|
+
* `platform.fe.<type>.<platform.component>.<name>` (e.g.
|
|
24
|
+
* `platform.fe.operation.editor-plugin-autocomplete.slow-lane-fetch`), so
|
|
25
|
+
* names here should be short, dot-free, and must not repeat the component
|
|
26
|
+
* name. This matches the convention used by `@atlaskit/emoji`'s
|
|
27
|
+
* `ufoExperiences` map (e.g. `'emoji-rendered'`).
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Only experiences with meaningful async work and a real success/failure
|
|
31
|
+
* outcome belong on UFO (which measures latency and success rate for FE
|
|
32
|
+
* Reliability SLOs). Per-keystroke suggestion lifecycle counters (viewed /
|
|
33
|
+
* inserted / dismissed) are tracked via analytics-next instead — they would
|
|
34
|
+
* otherwise emit zero-duration events on every word boundary.
|
|
35
|
+
*/
|
|
36
|
+
export const EXPERIENCE_NAME = {
|
|
37
|
+
SLOW_LANE_FETCH: 'slow-lane-fetch',
|
|
38
|
+
LOAD_VOCABULARY: 'load-vocabulary',
|
|
39
|
+
LOAD_VECTORS: 'load-vectors',
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export type AutocompleteExperienceName =
|
|
43
|
+
(typeof EXPERIENCE_NAME)[keyof typeof EXPERIENCE_NAME];
|
|
44
|
+
|
|
45
|
+
const isUfoEnabled = (): boolean => !process?.env?.REACT_SSR;
|
|
46
|
+
|
|
47
|
+
const operationConfig = {
|
|
48
|
+
platform: { component: 'editor-plugin-autocomplete' },
|
|
49
|
+
type: ExperienceTypes.Operation,
|
|
50
|
+
performanceType: ExperiencePerformanceTypes.Custom,
|
|
51
|
+
performanceConfig: {
|
|
52
|
+
histogram: {
|
|
53
|
+
[ExperiencePerformanceTypes.Custom]: {
|
|
54
|
+
duration: '50_100_250_500_1000_2000_4000',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const experiences: Record<AutocompleteExperienceName, ConcurrentExperience> = {
|
|
61
|
+
[EXPERIENCE_NAME.SLOW_LANE_FETCH]: new ConcurrentExperience(
|
|
62
|
+
EXPERIENCE_NAME.SLOW_LANE_FETCH,
|
|
63
|
+
operationConfig,
|
|
64
|
+
),
|
|
65
|
+
[EXPERIENCE_NAME.LOAD_VOCABULARY]: new ConcurrentExperience(
|
|
66
|
+
EXPERIENCE_NAME.LOAD_VOCABULARY,
|
|
67
|
+
operationConfig,
|
|
68
|
+
),
|
|
69
|
+
[EXPERIENCE_NAME.LOAD_VECTORS]: new ConcurrentExperience(
|
|
70
|
+
EXPERIENCE_NAME.LOAD_VECTORS,
|
|
71
|
+
operationConfig,
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const startExp = (
|
|
76
|
+
name: AutocompleteExperienceName,
|
|
77
|
+
id: string,
|
|
78
|
+
metadata?: CustomData,
|
|
79
|
+
): void => {
|
|
80
|
+
if (!isUfoEnabled()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const instance = experiences[name].getInstance(id);
|
|
85
|
+
instance.start();
|
|
86
|
+
if (metadata) {
|
|
87
|
+
instance.addMetadata(metadata);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// UFO errors must never break the plugin
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const succeedExp = (
|
|
95
|
+
name: AutocompleteExperienceName,
|
|
96
|
+
id: string,
|
|
97
|
+
metadata?: CustomData,
|
|
98
|
+
): void => {
|
|
99
|
+
if (!isUfoEnabled()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
experiences[name].getInstance(id).success({ metadata });
|
|
104
|
+
} catch {
|
|
105
|
+
// UFO errors must never break the plugin
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const failExp = (
|
|
110
|
+
name: AutocompleteExperienceName,
|
|
111
|
+
id: string,
|
|
112
|
+
metadata?: CustomData,
|
|
113
|
+
): void => {
|
|
114
|
+
if (!isUfoEnabled()) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
experiences[name].getInstance(id).failure({ metadata });
|
|
119
|
+
} catch {
|
|
120
|
+
// UFO errors must never break the plugin
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const abortExp = (
|
|
125
|
+
name: AutocompleteExperienceName,
|
|
126
|
+
id: string,
|
|
127
|
+
reason?: string,
|
|
128
|
+
): void => {
|
|
129
|
+
if (!isUfoEnabled()) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
experiences[name]
|
|
134
|
+
.getInstance(id)
|
|
135
|
+
.abort(reason ? { metadata: { reason } } : undefined);
|
|
136
|
+
} catch {
|
|
137
|
+
// UFO errors must never break the plugin
|
|
138
|
+
}
|
|
139
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* eslint-disable @atlaskit/editor/no-re-export */
|
|
2
|
+
export { autocompletePluginKey, createAutocompletePlugin } from '../pm-plugins/autocomplete-plugin';
|
|
3
|
+
export type {
|
|
4
|
+
AutocompleteContext,
|
|
5
|
+
AutocompletePluginOptions,
|
|
6
|
+
AutocompletePluginState,
|
|
7
|
+
} from '../pm-plugins/autocomplete-plugin';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* eslint-disable @atlaskit/editor/no-re-export */
|
|
2
|
+
export {
|
|
3
|
+
createSlowLaneClient,
|
|
4
|
+
getStoredContextVector,
|
|
5
|
+
getStoredLmLogits,
|
|
6
|
+
isWordBoundary,
|
|
7
|
+
setDefaultSlowLaneClient,
|
|
8
|
+
} from '../pm-plugins/slow-lane-client';
|
|
9
|
+
export type {
|
|
10
|
+
SlowLaneClientConfig,
|
|
11
|
+
TypeaheadEncodingsRequest,
|
|
12
|
+
TypeaheadEncodingsResponse,
|
|
13
|
+
} from '../pm-plugins/slow-lane-client';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable @atlaskit/editor/no-re-export */
|
|
2
|
+
export {
|
|
3
|
+
getLastPredictionDebug,
|
|
4
|
+
getPredictorStatus,
|
|
5
|
+
incrementSessionFreq,
|
|
6
|
+
ingestDocumentPage,
|
|
7
|
+
initL3Vocabulary,
|
|
8
|
+
initVectors,
|
|
9
|
+
initVocabulary,
|
|
10
|
+
loadDefaultVocabulary,
|
|
11
|
+
loadVectorsAsync,
|
|
12
|
+
predict,
|
|
13
|
+
} from '../pm-plugins/text-predictor';
|
|
14
|
+
export type { TenantVocabulary, WeightedTerm } from '../pm-plugins/text-predictor';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete/src/pm-plugins/autocomplete-plugin",
|
|
3
|
-
"main": "../../../dist/cjs/pm-plugins
|
|
4
|
-
"module": "../../../dist/esm/pm-plugins
|
|
5
|
-
"module:es2019": "../../../dist/es2019/pm-plugins
|
|
3
|
+
"main": "../../../dist/cjs/entry-points/src-pm-plugins-autocomplete-plugin.js",
|
|
4
|
+
"module": "../../../dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js",
|
|
5
|
+
"module:es2019": "../../../dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js",
|
|
6
6
|
"sideEffects": false,
|
|
7
|
-
"types": "../../../dist/types/pm-plugins
|
|
7
|
+
"types": "../../../dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts",
|
|
8
8
|
"typesVersions": {
|
|
9
9
|
">=4.5 <5.9": {
|
|
10
10
|
"*": [
|
|
11
|
-
"../../../dist/types-ts4.5/pm-plugins
|
|
11
|
+
"../../../dist/types-ts4.5/entry-points/src-pm-plugins-autocomplete-plugin.d.ts"
|
|
12
12
|
]
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -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,15 @@ 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 =>
|
|
35
|
+
'destroy' in client && typeof client.destroy === 'function';
|
|
36
|
+
|
|
32
37
|
export interface AutocompletePluginState {
|
|
33
38
|
/** The decoration set containing the ghost text widget */
|
|
34
39
|
decorationSet: DecorationSet;
|
|
@@ -97,14 +102,18 @@ const setAutocompleteMeta = (
|
|
|
97
102
|
let lastShownGhostText = '';
|
|
98
103
|
|
|
99
104
|
const showGhostText = (view: EditorView, text: string, position: number): void => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
try {
|
|
106
|
+
const { state, dispatch } = view;
|
|
107
|
+
const decorationSet = createGhostTextDecorationSet(state, position, text);
|
|
108
|
+
const tr = setAutocompleteMeta(state.tr, {
|
|
109
|
+
ghostText: text,
|
|
110
|
+
ghostPosition: position,
|
|
111
|
+
decorationSet,
|
|
112
|
+
});
|
|
113
|
+
dispatch(tr);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/showGhostText' });
|
|
116
|
+
}
|
|
108
117
|
};
|
|
109
118
|
|
|
110
119
|
/**
|
|
@@ -117,12 +126,17 @@ const clearGhostText = (state: EditorState, dispatch?: (tr: Transaction) => void
|
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
if (dispatch) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
try {
|
|
130
|
+
const tr = setAutocompleteMeta(state.tr, {
|
|
131
|
+
ghostText: '',
|
|
132
|
+
ghostPosition: -1,
|
|
133
|
+
decorationSet: DecorationSet.empty,
|
|
134
|
+
});
|
|
135
|
+
dispatch(tr);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/clearGhostText' });
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
126
140
|
}
|
|
127
141
|
return true;
|
|
128
142
|
};
|
|
@@ -137,14 +151,19 @@ const acceptGhostText = (state: EditorState, dispatch?: (tr: Transaction) => voi
|
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
if (dispatch) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
try {
|
|
155
|
+
const { ghostText, ghostPosition } = pluginState;
|
|
156
|
+
let tr = state.tr.insertText(ghostText, ghostPosition);
|
|
157
|
+
tr = setAutocompleteMeta(tr, {
|
|
158
|
+
ghostText: '',
|
|
159
|
+
ghostPosition: -1,
|
|
160
|
+
decorationSet: DecorationSet.empty,
|
|
161
|
+
});
|
|
162
|
+
dispatch(tr);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logException(error as Error, { location: 'editor-plugin-autocomplete/acceptGhostText' });
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
148
167
|
}
|
|
149
168
|
return true;
|
|
150
169
|
};
|
|
@@ -178,6 +197,11 @@ export interface AutocompletePluginOptions {
|
|
|
178
197
|
* Use this to serve vectors from a CDN or media service in production.
|
|
179
198
|
*/
|
|
180
199
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
200
|
+
/**
|
|
201
|
+
* When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
|
|
202
|
+
* the network-based slow-lane backend. Defaults to false (network client).
|
|
203
|
+
*/
|
|
204
|
+
useLocalModel?: boolean;
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
/**
|
|
@@ -235,6 +259,15 @@ export const createAutocompletePlugin = (
|
|
|
235
259
|
let lastSuggestionTypedLength = 0;
|
|
236
260
|
let lastSuggestionLength = 0;
|
|
237
261
|
|
|
262
|
+
const fireSuggestionDismissedAnalytics = (reason: 'escape' | 'blur'): void => {
|
|
263
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
264
|
+
action: ACTION.SUGGESTION_DISMISSED,
|
|
265
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
266
|
+
eventType: EVENT_TYPE.TRACK,
|
|
267
|
+
attributes: { reason },
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
238
271
|
const fireSuggestionInsertedAnalytics = (ghostText: string): void => {
|
|
239
272
|
const typedLength = lastSuggestionTypedLength;
|
|
240
273
|
const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
|
|
@@ -252,10 +285,14 @@ export const createAutocompletePlugin = (
|
|
|
252
285
|
});
|
|
253
286
|
};
|
|
254
287
|
|
|
255
|
-
const slowLaneClient =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
288
|
+
const slowLaneClient = options?.useLocalModel
|
|
289
|
+
? createLocalSlowLaneClient({
|
|
290
|
+
debounceMs: 300,
|
|
291
|
+
})
|
|
292
|
+
: createSlowLaneClient({
|
|
293
|
+
baseUrl: '',
|
|
294
|
+
debounceMs: 300,
|
|
295
|
+
});
|
|
259
296
|
setDefaultSlowLaneClient(slowLaneClient);
|
|
260
297
|
|
|
261
298
|
/**
|
|
@@ -269,45 +306,51 @@ export const createAutocompletePlugin = (
|
|
|
269
306
|
}
|
|
270
307
|
|
|
271
308
|
debounceTimer = setTimeout(() => {
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
try {
|
|
310
|
+
const { state } = view;
|
|
311
|
+
const { selection } = state;
|
|
274
312
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
313
|
+
// Only predict for cursor selections (not range selections)
|
|
314
|
+
if (!selection.empty) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
279
317
|
|
|
280
|
-
|
|
318
|
+
const textBefore = getTextBeforeCursor(state);
|
|
281
319
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
320
|
+
// Suppress re-showing the same suggestion the user just dismissed.
|
|
321
|
+
// Once the text context changes (user types or deletes), this clears automatically.
|
|
322
|
+
if (textBefore === dismissedContext) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
dismissedContext = null;
|
|
288
326
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
327
|
+
// Don't predict if there's not enough context
|
|
328
|
+
if (textBefore.trim().length < 3) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
293
331
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
332
|
+
// Tier 1 prediction is synchronous -- no async needed
|
|
333
|
+
const prediction = predict(textBefore);
|
|
334
|
+
|
|
335
|
+
if (prediction && prediction.length > 0) {
|
|
336
|
+
const typedLength = getTypedLengthForPrediction(textBefore);
|
|
337
|
+
lastSuggestionTypedLength = typedLength;
|
|
338
|
+
lastSuggestionLength = typedLength + prediction.length;
|
|
339
|
+
|
|
340
|
+
showGhostText(view, prediction, selection.from);
|
|
341
|
+
if (prediction !== lastShownGhostText) {
|
|
342
|
+
lastShownGhostText = prediction;
|
|
343
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
344
|
+
action: ACTION.SUGGESTION_VIEWED,
|
|
345
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
346
|
+
eventType: EVENT_TYPE.TRACK,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
310
349
|
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logException(error as Error, {
|
|
352
|
+
location: 'editor-plugin-autocomplete/schedulePrediction',
|
|
353
|
+
});
|
|
311
354
|
}
|
|
312
355
|
}, DEBOUNCE_MS);
|
|
313
356
|
};
|
|
@@ -424,6 +467,7 @@ export const createAutocompletePlugin = (
|
|
|
424
467
|
const didClear = clearGhostText(state, dispatch);
|
|
425
468
|
if (didClear) {
|
|
426
469
|
dismissedContext = getTextBeforeCursor(state);
|
|
470
|
+
fireSuggestionDismissedAnalytics('escape');
|
|
427
471
|
}
|
|
428
472
|
return didClear;
|
|
429
473
|
},
|
|
@@ -436,12 +480,23 @@ export const createAutocompletePlugin = (
|
|
|
436
480
|
| undefined;
|
|
437
481
|
if (pluginState?.ghostText) {
|
|
438
482
|
clearGhostText(view.state, view.dispatch);
|
|
483
|
+
fireSuggestionDismissedAnalytics('blur');
|
|
439
484
|
}
|
|
440
485
|
return false;
|
|
441
486
|
},
|
|
442
487
|
focus: () => {
|
|
443
|
-
|
|
444
|
-
|
|
488
|
+
try {
|
|
489
|
+
loadDefaultVocabulary();
|
|
490
|
+
} catch (error) {
|
|
491
|
+
logException(error as Error, {
|
|
492
|
+
location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
loadVectorsAsync({ getBinaryUrl: options?.getVectorsBinaryUrl }).catch((error) => {
|
|
496
|
+
logException(error as Error, {
|
|
497
|
+
location: 'editor-plugin-autocomplete/loadVectorsAsync',
|
|
498
|
+
});
|
|
499
|
+
});
|
|
445
500
|
if (!hasIngestedPage) {
|
|
446
501
|
hasIngestedPage = true;
|
|
447
502
|
if (options?.getContext) {
|
|
@@ -461,7 +516,11 @@ export const createAutocompletePlugin = (
|
|
|
461
516
|
}
|
|
462
517
|
context.siblingCommentsContents?.forEach(ingestDocumentPage);
|
|
463
518
|
})
|
|
464
|
-
.catch(() => {
|
|
519
|
+
.catch((error) => {
|
|
520
|
+
logException(error as Error, {
|
|
521
|
+
location: 'editor-plugin-autocomplete/getContext',
|
|
522
|
+
});
|
|
523
|
+
});
|
|
465
524
|
}
|
|
466
525
|
}
|
|
467
526
|
return false;
|
|
@@ -503,6 +562,9 @@ export const createAutocompletePlugin = (
|
|
|
503
562
|
if (debounceTimer) {
|
|
504
563
|
clearTimeout(debounceTimer);
|
|
505
564
|
}
|
|
565
|
+
if (hasDestroy(slowLaneClient)) {
|
|
566
|
+
slowLaneClient.destroy();
|
|
567
|
+
}
|
|
506
568
|
setDefaultSlowLaneClient(null);
|
|
507
569
|
},
|
|
508
570
|
}),
|