@atlaskit/editor-plugin-autocomplete 4.0.2 → 4.0.4
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 +26 -0
- package/dist/cjs/analytics/ufo.js +13 -7
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +22 -2
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +37 -10
- package/dist/cjs/pm-plugins/slow-lane-client.js +11 -5
- package/dist/cjs/pm-plugins/text-predictor.js +2 -2
- package/dist/es2019/analytics/ufo.js +13 -4
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +68 -46
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +36 -6
- package/dist/es2019/pm-plugins/slow-lane-client.js +11 -5
- package/dist/es2019/pm-plugins/text-predictor.js +2 -2
- package/dist/esm/analytics/ufo.js +13 -5
- package/dist/esm/pm-plugins/autocomplete-plugin.js +22 -2
- package/dist/esm/pm-plugins/local-slow-lane-client.js +37 -10
- package/dist/esm/pm-plugins/slow-lane-client.js +11 -5
- package/dist/esm/pm-plugins/text-predictor.js +2 -2
- package/dist/types/analytics/ufo.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +6 -0
- package/package.json +2 -2
- package/src/analytics/ufo.ts +13 -2
- package/src/index.ts +4 -0
- package/src/pm-plugins/autocomplete-plugin.ts +82 -54
- package/src/pm-plugins/local-slow-lane-client.ts +44 -5
- package/src/pm-plugins/slow-lane-client.ts +13 -3
- package/src/pm-plugins/text-predictor.ts +2 -2
|
@@ -560,7 +560,7 @@ export const predict = textBefore => {
|
|
|
560
560
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
561
561
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
562
562
|
*/
|
|
563
|
-
|
|
563
|
+
function unwrapJsonModule(mod, shape) {
|
|
564
564
|
if (mod == null || typeof mod !== 'object') {
|
|
565
565
|
return null;
|
|
566
566
|
}
|
|
@@ -585,7 +585,7 @@ const unwrapJsonModule = (mod, shape) => {
|
|
|
585
585
|
return namespace.default;
|
|
586
586
|
}
|
|
587
587
|
return null;
|
|
588
|
-
}
|
|
588
|
+
}
|
|
589
589
|
export const loadVectorsAsync = async options => {
|
|
590
590
|
var _options$isLocalLLM;
|
|
591
591
|
if (vectorStore || vectorsLoadStarted) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
2
4
|
/**
|
|
3
5
|
* UFO experience tracking helpers for `@atlaskit/editor-plugin-autocomplete`.
|
|
4
6
|
*
|
|
@@ -89,15 +91,21 @@ export var failExp = function failExp(name, id, metadata) {
|
|
|
89
91
|
// UFO errors must never break the plugin
|
|
90
92
|
}
|
|
91
93
|
};
|
|
92
|
-
export var abortExp = function abortExp(name, id, reason) {
|
|
94
|
+
export var abortExp = function abortExp(name, id, reason, metadata) {
|
|
93
95
|
if (!isUfoEnabled()) {
|
|
94
96
|
return;
|
|
95
97
|
}
|
|
96
98
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
// `reason` is the dedicated, canonical channel for the abort reason and
|
|
100
|
+
// intentionally takes precedence over a `reason` key in `metadata`. Callers
|
|
101
|
+
// must pass the reason via the param, not inside `metadata` (the `reason`
|
|
102
|
+
// key there is reserved and would be overwritten).
|
|
103
|
+
var merged = _objectSpread(_objectSpread({}, metadata), reason ? {
|
|
104
|
+
reason: reason
|
|
105
|
+
} : {});
|
|
106
|
+
var hasMetadata = Object.keys(merged).length > 0;
|
|
107
|
+
experiences[name].getInstance(id).abort(hasMetadata ? {
|
|
108
|
+
metadata: merged
|
|
101
109
|
} : undefined);
|
|
102
110
|
} catch (_unused4) {
|
|
103
111
|
// UFO errors must never break the plugin
|
|
@@ -242,6 +242,7 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
242
242
|
* user has already typed several words before the promise resolved.
|
|
243
243
|
*/
|
|
244
244
|
var currentView = null;
|
|
245
|
+
var unsubscribeFromContextUpdates;
|
|
245
246
|
/**
|
|
246
247
|
* Set after accepting a suggestion so the next doc-change update
|
|
247
248
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -663,7 +664,21 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
663
664
|
}
|
|
664
665
|
}
|
|
665
666
|
},
|
|
666
|
-
view: function view() {
|
|
667
|
+
view: function view(editorView) {
|
|
668
|
+
// Capture up front so a subscription notification before the first PM
|
|
669
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
670
|
+
currentView = editorView;
|
|
671
|
+
|
|
672
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
673
|
+
if (isAutocompleteEnabled && options !== null && options !== void 0 && options.subscribeToContextUpdates) {
|
|
674
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(function () {
|
|
675
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
676
|
+
refreshContext({
|
|
677
|
+
source: 'subscription',
|
|
678
|
+
allowThrottle: false
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
667
682
|
return {
|
|
668
683
|
update: function update(view, prevState) {
|
|
669
684
|
if (!isAutocompleteEnabled) {
|
|
@@ -692,7 +707,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
692
707
|
// thread still loading). Retry on word boundaries until we have
|
|
693
708
|
// the parent comment, throttled so we don't refetch constantly
|
|
694
709
|
// and capped so non-comment editors stop retrying entirely.
|
|
695
|
-
|
|
710
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
711
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
712
|
+
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((_resolvedContext = resolvedContext) !== null && _resolvedContext !== void 0 && _resolvedContext.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
696
713
|
// Only count the attempt when a fetch actually started, so an
|
|
697
714
|
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
698
715
|
if (refreshContext({
|
|
@@ -706,8 +723,11 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
706
723
|
}
|
|
707
724
|
},
|
|
708
725
|
destroy: function destroy() {
|
|
726
|
+
var _unsubscribeFromConte;
|
|
709
727
|
destroyed = true;
|
|
710
728
|
currentView = null;
|
|
729
|
+
(_unsubscribeFromConte = unsubscribeFromContextUpdates) === null || _unsubscribeFromConte === void 0 || _unsubscribeFromConte();
|
|
730
|
+
unsubscribeFromContextUpdates = undefined;
|
|
711
731
|
if (debounceTimer) {
|
|
712
732
|
clearTimeout(debounceTimer);
|
|
713
733
|
}
|
|
@@ -48,6 +48,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
48
48
|
* asynchronously after each updateContext() call.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
+
import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
51
52
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
52
53
|
import { isWordBoundary } from './slow-lane-client';
|
|
53
54
|
|
|
@@ -257,7 +258,7 @@ var bePayloadDataPromise;
|
|
|
257
258
|
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
258
259
|
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
259
260
|
*/
|
|
260
|
-
|
|
261
|
+
function unwrapJsonModule(mod, shape) {
|
|
261
262
|
if (mod == null || _typeof(mod) !== 'object') {
|
|
262
263
|
return null;
|
|
263
264
|
}
|
|
@@ -309,7 +310,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
309
310
|
return namespace.default;
|
|
310
311
|
}
|
|
311
312
|
return null;
|
|
312
|
-
}
|
|
313
|
+
}
|
|
313
314
|
|
|
314
315
|
/**
|
|
315
316
|
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
@@ -947,17 +948,23 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
947
948
|
*/
|
|
948
949
|
var runInference = /*#__PURE__*/function () {
|
|
949
950
|
var _ref0 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(text, requestId) {
|
|
950
|
-
var lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
|
|
951
|
+
var experienceId, lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
|
|
951
952
|
return _regeneratorRuntime.wrap(function (_context4) {
|
|
952
953
|
while (1) switch (_context4.prev = _context4.next) {
|
|
953
954
|
case 0:
|
|
955
|
+
captureCompletionTime = function _captureCompletionTim(promise, onResolved) {
|
|
956
|
+
return promise.then(function (value) {
|
|
957
|
+
onResolved(performance.now());
|
|
958
|
+
return value;
|
|
959
|
+
});
|
|
960
|
+
};
|
|
954
961
|
if (!(!engine || destroyed)) {
|
|
955
962
|
_context4.next = 1;
|
|
956
963
|
break;
|
|
957
964
|
}
|
|
958
965
|
return _context4.abrupt("return");
|
|
959
966
|
case 1:
|
|
960
|
-
// Clear the capture buffer so we read only this pass's logits. The engine
|
|
967
|
+
experienceId = String(requestId); // Clear the capture buffer so we read only this pass's logits. The engine
|
|
961
968
|
// serialises per-model requests and updateContext is debounced, so the
|
|
962
969
|
// latest request's decode step is the last to populate `captured` before
|
|
963
970
|
// we read it below; stale requests bail on the latestRequestId guard.
|
|
@@ -970,12 +977,6 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
970
977
|
lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
|
|
971
978
|
semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
|
|
972
979
|
arcticInput = wrapForArctic(semanticText);
|
|
973
|
-
captureCompletionTime = function captureCompletionTime(promise, onResolved) {
|
|
974
|
-
return promise.then(function (value) {
|
|
975
|
-
onResolved(performance.now());
|
|
976
|
-
return value;
|
|
977
|
-
});
|
|
978
|
-
};
|
|
979
980
|
if (isAutocompleteDebugEnabled()) {
|
|
980
981
|
// eslint-disable-next-line no-console
|
|
981
982
|
console.log("%c[LocalSlowLane] %c\uD83D\uDD22 Arctic input (".concat(arcticInput.length, " chars, ").concat(splitOnWhitespace(semanticText).length, " words): \"").concat(arcticInput.length > 100 ? "".concat(arcticInput.slice(0, 100), "\u2026") : arcticInput, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
|
|
@@ -983,6 +984,16 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
983
984
|
console.log("%c[LocalSlowLane] %c\uD83E\uDDE0 LM input (".concat(lmText.length, " chars, ").concat(splitOnWhitespace(lmText).length, " words): \"").concat(lmText.length > 100 ? "".concat(lmText.slice(0, 100), "\u2026") : lmText, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
|
|
984
985
|
}
|
|
985
986
|
_context4.prev = 2;
|
|
987
|
+
// Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
|
|
988
|
+
// matching LOAD_VECTORS/LOAD_VOCABULARY) so on-device inference
|
|
989
|
+
// latency/success-rate feeds the same FE Reliability SLO. Started inside
|
|
990
|
+
// the try — post engine-init (parity with the network fetch, which
|
|
991
|
+
// excludes the one-time model load) and so the catch below always
|
|
992
|
+
// terminates the experience, even on a synchronous throw.
|
|
993
|
+
startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
|
|
994
|
+
textLength: text.length,
|
|
995
|
+
isLocalLLM: true
|
|
996
|
+
});
|
|
986
997
|
tStart = performance.now();
|
|
987
998
|
tLmDone = 0;
|
|
988
999
|
tEmbDone = 0;
|
|
@@ -1015,6 +1026,9 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
1015
1026
|
_context4.next = 4;
|
|
1016
1027
|
break;
|
|
1017
1028
|
}
|
|
1029
|
+
abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
|
|
1030
|
+
isLocalLLM: true
|
|
1031
|
+
});
|
|
1018
1032
|
return _context4.abrupt("return");
|
|
1019
1033
|
case 4:
|
|
1020
1034
|
// ── LM logits: whole-word BE-parity payload ──────────────────
|
|
@@ -1066,6 +1080,12 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
1066
1080
|
// eslint-disable-next-line no-console
|
|
1067
1081
|
console.groupEnd();
|
|
1068
1082
|
}
|
|
1083
|
+
succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
|
|
1084
|
+
textLength: text.length,
|
|
1085
|
+
hasVector: storedContextVector !== null,
|
|
1086
|
+
hasLmLogits: storedLmLogits !== null,
|
|
1087
|
+
isLocalLLM: true
|
|
1088
|
+
});
|
|
1069
1089
|
onUpdate === null || onUpdate === void 0 || onUpdate({
|
|
1070
1090
|
textLength: text.length,
|
|
1071
1091
|
hasVector: storedContextVector !== null,
|
|
@@ -1080,10 +1100,17 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
1080
1100
|
_context4.next = 6;
|
|
1081
1101
|
break;
|
|
1082
1102
|
}
|
|
1103
|
+
abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
|
|
1104
|
+
isLocalLLM: true
|
|
1105
|
+
});
|
|
1083
1106
|
return _context4.abrupt("return");
|
|
1084
1107
|
case 6:
|
|
1085
1108
|
storedContextVector = null;
|
|
1086
1109
|
storedLmLogits = null;
|
|
1110
|
+
failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
|
|
1111
|
+
errorType: 'inference',
|
|
1112
|
+
isLocalLLM: true
|
|
1113
|
+
});
|
|
1087
1114
|
onUpdate === null || onUpdate === void 0 || onUpdate({
|
|
1088
1115
|
textLength: text.length,
|
|
1089
1116
|
hasVector: false,
|
|
@@ -81,7 +81,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
|
|
|
81
81
|
session_id: sessionId
|
|
82
82
|
};
|
|
83
83
|
startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
|
|
84
|
-
textLength: text.length
|
|
84
|
+
textLength: text.length,
|
|
85
|
+
isLocalLLM: false
|
|
85
86
|
});
|
|
86
87
|
if (isAutocompleteDebugEnabled()) {
|
|
87
88
|
// eslint-disable-next-line no-console
|
|
@@ -110,7 +111,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
|
|
|
110
111
|
storedLmLogits = null;
|
|
111
112
|
failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
|
|
112
113
|
status: res.status,
|
|
113
|
-
errorType: 'http_error'
|
|
114
|
+
errorType: 'http_error',
|
|
115
|
+
isLocalLLM: false
|
|
114
116
|
});
|
|
115
117
|
if (isAutocompleteDebugEnabled()) {
|
|
116
118
|
// eslint-disable-next-line no-console
|
|
@@ -145,7 +147,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
|
|
|
145
147
|
succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
|
|
146
148
|
textLength: text.length,
|
|
147
149
|
hasVector: storedContextVector !== null,
|
|
148
|
-
hasLmLogits: storedLmLogits !== null
|
|
150
|
+
hasLmLogits: storedLmLogits !== null,
|
|
151
|
+
isLocalLLM: false
|
|
149
152
|
});
|
|
150
153
|
onUpdate === null || onUpdate === void 0 || onUpdate({
|
|
151
154
|
textLength: text.length,
|
|
@@ -161,7 +164,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
|
|
|
161
164
|
storedContextVector = null;
|
|
162
165
|
storedLmLogits = null;
|
|
163
166
|
failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
|
|
164
|
-
errorType: 'network'
|
|
167
|
+
errorType: 'network',
|
|
168
|
+
isLocalLLM: false
|
|
165
169
|
});
|
|
166
170
|
if (isAutocompleteDebugEnabled()) {
|
|
167
171
|
// eslint-disable-next-line no-console
|
|
@@ -191,7 +195,9 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
|
|
|
191
195
|
debounceTimer = setTimeout(function () {
|
|
192
196
|
debounceTimer = null;
|
|
193
197
|
if (inflightRequestId !== null) {
|
|
194
|
-
abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded'
|
|
198
|
+
abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded', {
|
|
199
|
+
isLocalLLM: false
|
|
200
|
+
});
|
|
195
201
|
}
|
|
196
202
|
var requestId = String(++requestSeq);
|
|
197
203
|
inflightRequestId = requestId;
|
|
@@ -690,7 +690,7 @@ export var predict = function predict(textBefore) {
|
|
|
690
690
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
691
691
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
692
692
|
*/
|
|
693
|
-
|
|
693
|
+
function unwrapJsonModule(mod, shape) {
|
|
694
694
|
if (mod == null || _typeof(mod) !== 'object') {
|
|
695
695
|
return null;
|
|
696
696
|
}
|
|
@@ -726,7 +726,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
726
726
|
return namespace.default;
|
|
727
727
|
}
|
|
728
728
|
return null;
|
|
729
|
-
}
|
|
729
|
+
}
|
|
730
730
|
export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
731
731
|
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
|
|
732
732
|
var _options$isLocalLLM;
|
|
@@ -35,4 +35,4 @@ export type AutocompleteExperienceName = (typeof EXPERIENCE_NAME)[keyof typeof E
|
|
|
35
35
|
export declare const startExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
|
|
36
36
|
export declare const succeedExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
|
|
37
37
|
export declare const failExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
|
|
38
|
-
export declare const abortExp: (name: AutocompleteExperienceName, id: string, reason?: string) => void;
|
|
38
|
+
export declare const abortExp: (name: AutocompleteExperienceName, id: string, reason?: string, metadata?: CustomData) => void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface AutocompletePluginOptions {
|
|
|
40
40
|
* Use this to serve vectors from a CDN or media service in production.
|
|
41
41
|
*/
|
|
42
42
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Subscription for hosts with live external context (e.g. Rovo chat). The plugin
|
|
45
|
+
* re-fetches via getContext() on each notification and unsubscribes on destroy.
|
|
46
|
+
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
47
|
+
*/
|
|
48
|
+
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
43
49
|
/**
|
|
44
50
|
* User locale used to determine whether autocomplete should run.
|
|
45
51
|
* Defaults to browser locale when omitted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"wink-nlp": "^2.4.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@atlaskit/editor-common": "^116.
|
|
30
|
+
"@atlaskit/editor-common": "^116.15.0",
|
|
31
31
|
"@atlaskit/editor-plugin-analytics": "^12.0.0",
|
|
32
32
|
"react": "^18.2.0"
|
|
33
33
|
},
|
package/src/analytics/ufo.ts
CHANGED
|
@@ -120,12 +120,23 @@ export const failExp = (
|
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
-
export const abortExp = (
|
|
123
|
+
export const abortExp = (
|
|
124
|
+
name: AutocompleteExperienceName,
|
|
125
|
+
id: string,
|
|
126
|
+
reason?: string,
|
|
127
|
+
metadata?: CustomData,
|
|
128
|
+
): void => {
|
|
124
129
|
if (!isUfoEnabled()) {
|
|
125
130
|
return;
|
|
126
131
|
}
|
|
127
132
|
try {
|
|
128
|
-
|
|
133
|
+
// `reason` is the dedicated, canonical channel for the abort reason and
|
|
134
|
+
// intentionally takes precedence over a `reason` key in `metadata`. Callers
|
|
135
|
+
// must pass the reason via the param, not inside `metadata` (the `reason`
|
|
136
|
+
// key there is reserved and would be overwritten).
|
|
137
|
+
const merged: CustomData = { ...metadata, ...(reason ? { reason } : {}) };
|
|
138
|
+
const hasMetadata = Object.keys(merged).length > 0;
|
|
139
|
+
experiences[name].getInstance(id).abort(hasMetadata ? { metadata: merged } : undefined);
|
|
129
140
|
} catch {
|
|
130
141
|
// UFO errors must never break the plugin
|
|
131
142
|
}
|
package/src/index.ts
CHANGED
|
@@ -214,6 +214,12 @@ export interface AutocompletePluginOptions {
|
|
|
214
214
|
* Use this to serve vectors from a CDN or media service in production.
|
|
215
215
|
*/
|
|
216
216
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
217
|
+
/**
|
|
218
|
+
* Subscription for hosts with live external context (e.g. Rovo chat). The plugin
|
|
219
|
+
* re-fetches via getContext() on each notification and unsubscribes on destroy.
|
|
220
|
+
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
221
|
+
*/
|
|
222
|
+
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
217
223
|
/**
|
|
218
224
|
* User locale used to determine whether autocomplete should run.
|
|
219
225
|
* Defaults to browser locale when omitted.
|
|
@@ -329,6 +335,7 @@ export const createAutocompletePlugin = (
|
|
|
329
335
|
* user has already typed several words before the promise resolved.
|
|
330
336
|
*/
|
|
331
337
|
let currentView: EditorView | null = null;
|
|
338
|
+
let unsubscribeFromContextUpdates: (() => void) | undefined;
|
|
332
339
|
/**
|
|
333
340
|
* Set after accepting a suggestion so the next doc-change update
|
|
334
341
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -582,7 +589,9 @@ export const createAutocompletePlugin = (
|
|
|
582
589
|
// nodeAfter correctly handles inline atoms (mentions, emojis) where
|
|
583
590
|
// parentOffset and textContent indices diverge.
|
|
584
591
|
const nodeAfter = selection.$from.nodeAfter;
|
|
585
|
-
const charAfterCursor = nodeAfter?.isText
|
|
592
|
+
const charAfterCursor = nodeAfter?.isText
|
|
593
|
+
? getLeadingTextCharacter(nodeAfter.text)
|
|
594
|
+
: undefined;
|
|
586
595
|
// Only suppress for Unicode letters, digits, and underscore — hyphens,
|
|
587
596
|
// apostrophes, and similar punctuation are valid left-edge boundaries
|
|
588
597
|
// and should not block suggestions (e.g. cursor before '-' in compound-word).
|
|
@@ -790,66 +799,85 @@ export const createAutocompletePlugin = (
|
|
|
790
799
|
},
|
|
791
800
|
},
|
|
792
801
|
|
|
793
|
-
view: () =>
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
802
|
+
view: (editorView: EditorView) => {
|
|
803
|
+
// Capture up front so a subscription notification before the first PM
|
|
804
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
805
|
+
currentView = editorView;
|
|
798
806
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
dismissedContext = getTextBeforeCursor(view.state);
|
|
807
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
808
|
+
if (isAutocompleteEnabled && options?.subscribeToContextUpdates) {
|
|
809
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(() => {
|
|
810
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
811
|
+
refreshContext({ source: 'subscription', allowThrottle: false });
|
|
812
|
+
});
|
|
813
|
+
}
|
|
807
814
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
815
|
+
return {
|
|
816
|
+
update: (view: EditorView, prevState: EditorState) => {
|
|
817
|
+
if (!isAutocompleteEnabled) {
|
|
811
818
|
return;
|
|
812
819
|
}
|
|
813
820
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
// and capped so non-comment editors stop retrying entirely.
|
|
826
|
-
if (
|
|
827
|
-
!resolvedContext?.parentCommentContent &&
|
|
828
|
-
wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS
|
|
829
|
-
) {
|
|
830
|
-
// Only count the attempt when a fetch actually started, so an
|
|
831
|
-
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
832
|
-
if (refreshContext({ source: 'word-boundary' })) {
|
|
833
|
-
wordBoundaryRefreshAttempts++;
|
|
821
|
+
currentView = view;
|
|
822
|
+
if (!prevState.doc.eq(view.state.doc)) {
|
|
823
|
+
if (justAccepted) {
|
|
824
|
+
justAccepted = false;
|
|
825
|
+
|
|
826
|
+
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
827
|
+
// the dismissedContext guard and abort until the user types again.
|
|
828
|
+
dismissedContext = getTextBeforeCursor(view.state);
|
|
829
|
+
|
|
830
|
+
if (debounceTimer) {
|
|
831
|
+
clearTimeout(debounceTimer);
|
|
834
832
|
}
|
|
833
|
+
return;
|
|
835
834
|
}
|
|
836
|
-
}
|
|
837
835
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
836
|
+
maybeUpdateSessionFrequency(view, prevState);
|
|
837
|
+
|
|
838
|
+
const textBefore = getTextBeforeCursor(view.state);
|
|
839
|
+
if (isWordBoundary(textBefore)) {
|
|
840
|
+
slowLaneClient.updateContext(
|
|
841
|
+
buildSlowLaneText(view.state.doc.textContent, resolvedContext),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
// Context may not have resolved on first focus (e.g. comment
|
|
845
|
+
// thread still loading). Retry on word boundaries until we have
|
|
846
|
+
// the parent comment, throttled so we don't refetch constantly
|
|
847
|
+
// and capped so non-comment editors stop retrying entirely.
|
|
848
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
849
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
850
|
+
if (
|
|
851
|
+
!options?.subscribeToContextUpdates &&
|
|
852
|
+
!resolvedContext?.parentCommentContent &&
|
|
853
|
+
wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS
|
|
854
|
+
) {
|
|
855
|
+
// Only count the attempt when a fetch actually started, so an
|
|
856
|
+
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
857
|
+
if (refreshContext({ source: 'word-boundary' })) {
|
|
858
|
+
wordBoundaryRefreshAttempts++;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
schedulePrediction(view);
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
destroy: () => {
|
|
867
|
+
destroyed = true;
|
|
868
|
+
currentView = null;
|
|
869
|
+
unsubscribeFromContextUpdates?.();
|
|
870
|
+
unsubscribeFromContextUpdates = undefined;
|
|
871
|
+
if (debounceTimer) {
|
|
872
|
+
clearTimeout(debounceTimer);
|
|
873
|
+
}
|
|
874
|
+
if (hasDestroy(slowLaneClient)) {
|
|
875
|
+
slowLaneClient.destroy();
|
|
876
|
+
}
|
|
877
|
+
ingestedContextTexts.clear();
|
|
878
|
+
setDefaultSlowLaneClient(null);
|
|
879
|
+
},
|
|
880
|
+
};
|
|
881
|
+
},
|
|
854
882
|
});
|
|
855
883
|
};
|