@atlaskit/editor-plugin-autocomplete 9.0.0 → 9.2.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 +133 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +564 -67
- package/dist/cjs/pm-plugins/debug-mode.js +28 -2
- package/dist/cjs/pm-plugins/inline-code-harvester.js +576 -0
- package/dist/cjs/pm-plugins/text-predictor.js +437 -178
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +515 -52
- package/dist/es2019/pm-plugins/debug-mode.js +27 -1
- package/dist/es2019/pm-plugins/inline-code-harvester.js +455 -0
- package/dist/es2019/pm-plugins/text-predictor.js +223 -3
- package/dist/esm/pm-plugins/autocomplete-plugin.js +562 -67
- package/dist/esm/pm-plugins/debug-mode.js +27 -1
- package/dist/esm/pm-plugins/inline-code-harvester.js +572 -0
- package/dist/esm/pm-plugins/text-predictor.js +437 -178
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +37 -0
- package/dist/types/pm-plugins/debug-mode.d.ts +34 -0
- package/dist/types/pm-plugins/inline-code-harvester.d.ts +146 -0
- package/dist/types/pm-plugins/text-predictor.d.ts +105 -3
- package/package.json +2 -2
- package/src/pm-plugins/autocomplete-plugin.ts +589 -48
- package/src/pm-plugins/debug-mode.ts +47 -1
- package/src/pm-plugins/inline-code-harvester.ts +561 -0
- package/src/pm-plugins/text-predictor.ts +274 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
6
7
|
});
|
|
@@ -19,11 +20,13 @@ var _localSlowLaneClient = require("./local-slow-lane-client");
|
|
|
19
20
|
var _scoringPipeline = require("./scoring-pipeline");
|
|
20
21
|
var _slowLaneClient = require("./slow-lane-client");
|
|
21
22
|
var _textPredictor = require("./text-predictor");
|
|
23
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
22
24
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
23
25
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
24
26
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
25
27
|
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; }
|
|
26
|
-
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) { (0, _defineProperty2.default)(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; }
|
|
28
|
+
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) { (0, _defineProperty2.default)(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; } // Type-only, so the harvester chunk is still reached exclusively through the
|
|
29
|
+
// dynamic import in `ensureInlineCodeHarvester`.
|
|
27
30
|
var autocompletePluginKey = exports.autocompletePluginKey = new _state.PluginKey('autocomplete');
|
|
28
31
|
var PREDICTION_COALESCE_MS = 0;
|
|
29
32
|
var GHOST_DECISION_BUDGET_MS = 100;
|
|
@@ -34,6 +37,32 @@ var CONTEXT_REFRESH_THROTTLE_MS = 1000;
|
|
|
34
37
|
// version of the (potentially hundreds-of-KB) page content for the plugin's
|
|
35
38
|
// lifetime. Eviction is FIFO; a re-ingest of an evicted text is harmless.
|
|
36
39
|
var MAX_INGESTED_CONTEXT_TEXTS = 50;
|
|
40
|
+
// Re-prime callback of every mounted editor. The harvested set and the L1
|
|
41
|
+
// boosts are shared by all of them, and more than one is mounted routinely — a
|
|
42
|
+
// comment box alongside its replies, or a comment box alongside the chat input.
|
|
43
|
+
// Membership doubles as the mount count, so a teardown only clears the shared
|
|
44
|
+
// stores once the last editor has gone.
|
|
45
|
+
//
|
|
46
|
+
// Decision: one pot, shared across hosts. Terms harvested in a comment box can
|
|
47
|
+
// be offered in the chat input and the other way round. Both are content from
|
|
48
|
+
// the page the reader is looking at, so the mixing is between things they can
|
|
49
|
+
// already see; partitioning the boosts would mean a per-host overlay on every
|
|
50
|
+
// trie node, which the scoring path reads on every keystroke.
|
|
51
|
+
var mountedEditors = new Set();
|
|
52
|
+
// The scope that shared learning belongs to, as last reported by a host that
|
|
53
|
+
// scopes its context. Held here rather than per editor because the data it
|
|
54
|
+
// guards is shared and some hosts replace one editor with another across a
|
|
55
|
+
// navigation: an instance-local key would read as unset on exactly the change it
|
|
56
|
+
// exists to catch, while the previous scope's terms carried on in these globals.
|
|
57
|
+
// Undefined until a scoping host says otherwise. An editor that sends no key
|
|
58
|
+
// still shares the pot being emptied, so it is re-primed from the context it
|
|
59
|
+
// already holds rather than left with learning it can no longer see.
|
|
60
|
+
var contextScopeKey;
|
|
61
|
+
// Set by whichever editor first finished importing the harvester chunk. The
|
|
62
|
+
// reset paths go through this rather than their own copy, because the editor
|
|
63
|
+
// that has to clear the set is not always one that imported it — the set is one
|
|
64
|
+
// per module, and nothing can be in it unless some editor got this far.
|
|
65
|
+
var loadedInlineCodeHarvester = null;
|
|
37
66
|
// Caps how many times the word-boundary path will retry getContext() while the
|
|
38
67
|
// parent comment is still missing. Combined with the 1s throttle this gives a
|
|
39
68
|
// ~5s window to cover a still-loading comment thread, then stops permanently so
|
|
@@ -110,10 +139,14 @@ var advanceGhostThroughTypedCharacter = function advanceGhostThroughTypedCharact
|
|
|
110
139
|
ghostPosition: nextPosition,
|
|
111
140
|
decorationSet: (0, _ghostTextDecoration.createGhostTextDecorationSet)(tr.doc, nextPosition, remaining),
|
|
112
141
|
// `surface` stays the full candidate so cooldown and analytics still
|
|
113
|
-
// describe the suggestion that was originally committed.
|
|
142
|
+
// describe the suggestion that was originally committed. The replaced
|
|
143
|
+
// prefix grows with the keystroke: it is the run the insertion overwrites,
|
|
144
|
+
// and the user has just typed one more character of it.
|
|
114
145
|
suggestion: _objectSpread(_objectSpread({}, suggestion), {}, {
|
|
115
146
|
ghostText: remaining,
|
|
116
147
|
position: nextPosition
|
|
148
|
+
}, suggestion.replacesTypedPrefixLength === undefined ? {} : {
|
|
149
|
+
replacesTypedPrefixLength: suggestion.replacesTypedPrefixLength + 1
|
|
117
150
|
})
|
|
118
151
|
});
|
|
119
152
|
};
|
|
@@ -148,9 +181,12 @@ var getTextBeforeCursor = function getTextBeforeCursor(state) {
|
|
|
148
181
|
}
|
|
149
182
|
return fullText.slice(-maxChars);
|
|
150
183
|
};
|
|
151
|
-
var
|
|
184
|
+
var getTrailingSurfaceToken = function getTrailingSurfaceToken(text) {
|
|
152
185
|
var _text$trimEnd$match$, _text$trimEnd$match;
|
|
153
|
-
return (_text$trimEnd$match$ = (_text$trimEnd$match = text.trimEnd().match(TRAILING_SURFACE_PREFIX_REGEX)) === null || _text$trimEnd$match === void 0 ? void 0 : _text$trimEnd$match[0]
|
|
186
|
+
return (_text$trimEnd$match$ = (_text$trimEnd$match = text.trimEnd().match(TRAILING_SURFACE_PREFIX_REGEX)) === null || _text$trimEnd$match === void 0 ? void 0 : _text$trimEnd$match[0]) !== null && _text$trimEnd$match$ !== void 0 ? _text$trimEnd$match$ : '';
|
|
187
|
+
};
|
|
188
|
+
var getTrailingSurfacePrefixLength = function getTrailingSurfacePrefixLength(text) {
|
|
189
|
+
return getTrailingSurfaceToken(text).length;
|
|
154
190
|
};
|
|
155
191
|
|
|
156
192
|
/**
|
|
@@ -164,11 +200,13 @@ var setAutocompleteMeta = function setAutocompleteMeta(tr, meta) {
|
|
|
164
200
|
* Apply a ghost text suggestion to the editor state.
|
|
165
201
|
*/
|
|
166
202
|
var lastShownGhostText = '';
|
|
167
|
-
var showGhostText = function showGhostText(view, prediction, position, revision, decisionStartedAt) {
|
|
203
|
+
var showGhostText = function showGhostText(view, prediction, position, revision, decisionStartedAt, replacesTypedPrefixLength) {
|
|
168
204
|
try {
|
|
169
205
|
var state = view.state,
|
|
170
206
|
dispatch = view.dispatch;
|
|
171
|
-
var suggestion = {
|
|
207
|
+
var suggestion = _objectSpread(_objectSpread({}, replacesTypedPrefixLength === undefined ? {} : {
|
|
208
|
+
replacesTypedPrefixLength: replacesTypedPrefixLength
|
|
209
|
+
}), {}, {
|
|
172
210
|
decisionLatencyMs: performance.now() - decisionStartedAt,
|
|
173
211
|
evidenceTier: prediction.evidenceTier,
|
|
174
212
|
ghostText: prediction.text,
|
|
@@ -182,7 +220,7 @@ var showGhostText = function showGhostText(view, prediction, position, revision,
|
|
|
182
220
|
surface: prediction.surface,
|
|
183
221
|
termType: prediction.termType,
|
|
184
222
|
winnerMargin: prediction.winnerMargin
|
|
185
|
-
};
|
|
223
|
+
});
|
|
186
224
|
var decorationSet = (0, _ghostTextDecoration.createGhostTextDecorationSet)(state.doc, position, prediction.text);
|
|
187
225
|
var tr = setAutocompleteMeta(state.tr, {
|
|
188
226
|
ghostText: prediction.text,
|
|
@@ -237,10 +275,31 @@ var acceptGhostText = function acceptGhostText(state, dispatch) {
|
|
|
237
275
|
}
|
|
238
276
|
if (dispatch) {
|
|
239
277
|
try {
|
|
278
|
+
var _suggestion$replacesT;
|
|
240
279
|
var ghostText = pluginState.ghostText,
|
|
241
280
|
ghostPosition = pluginState.ghostPosition,
|
|
242
281
|
suggestion = pluginState.suggestion;
|
|
243
|
-
|
|
282
|
+
// Replacing back over the typed prefix rewrites it in the surface's own
|
|
283
|
+
// casing; every other path appends and leaves the prefix untouched.
|
|
284
|
+
var replaceFrom = Math.max(0, ghostPosition - Math.min((_suggestion$replacesT = suggestion.replacesTypedPrefixLength) !== null && _suggestion$replacesT !== void 0 ? _suggestion$replacesT : 0, ghostPosition));
|
|
285
|
+
var tr = state.tr;
|
|
286
|
+
if (replaceFrom === ghostPosition) {
|
|
287
|
+
tr = tr.insertText(ghostText, ghostPosition);
|
|
288
|
+
} else {
|
|
289
|
+
tr = tr.insertText(suggestion.surface, replaceFrom, ghostPosition);
|
|
290
|
+
// Only the harvested path replaces, and what authorized it was the
|
|
291
|
+
// surface being marked as code somewhere in the session. Inserting it
|
|
292
|
+
// as plain text loses that, so the identifier the user accepted reads
|
|
293
|
+
// as prose while the same identifier they typed by hand does not.
|
|
294
|
+
var codeMark = state.schema.marks.code;
|
|
295
|
+
if (codeMark) {
|
|
296
|
+
tr = tr.addMark(replaceFrom, replaceFrom + suggestion.surface.length, codeMark.create());
|
|
297
|
+
// The backtick input rule never ran, so nothing else will close this
|
|
298
|
+
// mark. Without dropping it from the stored set the next character
|
|
299
|
+
// the user types continues the code span.
|
|
300
|
+
tr = tr.removeStoredMark(codeMark);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
244
303
|
tr = setAutocompleteMeta(tr, {
|
|
245
304
|
ghostText: '',
|
|
246
305
|
ghostPosition: -1,
|
|
@@ -328,6 +387,71 @@ var getLeadingTextCharacter = function getLeadingTextCharacter(text) {
|
|
|
328
387
|
}
|
|
329
388
|
return String.fromCodePoint(firstCodePoint);
|
|
330
389
|
};
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The one verdict a harvested surface may speak on.
|
|
393
|
+
*
|
|
394
|
+
* `no-candidate` is the only abstention that means no vocabulary reaches the
|
|
395
|
+
* prefix at all. Every other one — a margin the model cannot clear, a rival
|
|
396
|
+
* still being read — describes known words in contention, where a session
|
|
397
|
+
* surface with no score behind it would be overruling the ranker rather than
|
|
398
|
+
* filling a gap it left.
|
|
399
|
+
*/
|
|
400
|
+
var HARVEST_ELIGIBLE_ABSTAIN_REASON = 'no-candidate';
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Whether the scored path has finished with this exact prefix and found nothing.
|
|
404
|
+
*
|
|
405
|
+
* The 100ms deadline expiring is not the same answer: it means the ranker was
|
|
406
|
+
* still working, and displaying then would race a suggestion that is about to
|
|
407
|
+
* arrive. So the harvest path reads the recorded verdict rather than the clock.
|
|
408
|
+
*/
|
|
409
|
+
var scoredPathFinishedEmpty = function scoredPathFinishedEmpty(textBefore) {
|
|
410
|
+
var outcome = (0, _textPredictor.getLastPredictionOutcome)();
|
|
411
|
+
return outcome !== null && outcome.textBefore === textBefore && !outcome.awaitingAsyncEvidence && outcome.abstainReason === HARVEST_ELIGIBLE_ABSTAIN_REASON;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Whether the surface is already sitting immediately before the typed prefix.
|
|
416
|
+
*
|
|
417
|
+
* Completing `ml-s` to `ml-studio` right after `ml-studio` produces the echo the
|
|
418
|
+
* scored path's repetition guard exists to stop, and this path does not go
|
|
419
|
+
* through arbitration to inherit it.
|
|
420
|
+
*/
|
|
421
|
+
var repeatsPrecedingText = function repeatsPrecedingText(match, textBefore) {
|
|
422
|
+
var trimmed = textBefore.trimEnd();
|
|
423
|
+
var preceding = trimmed.slice(0, trimmed.length - match.typedPrefixLength).trimEnd();
|
|
424
|
+
return preceding.toLowerCase().endsWith(match.surface.toLowerCase());
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Dress a harvested match as a prediction so it commits through the same path as
|
|
429
|
+
* a scored one.
|
|
430
|
+
*
|
|
431
|
+
* The scoring fields are zeroed rather than invented: there is no posterior, no
|
|
432
|
+
* margin and no shortlist behind this surface, and `session-harvest` on the
|
|
433
|
+
* evidence tier is what says so wherever the snapshot is read.
|
|
434
|
+
*/
|
|
435
|
+
var buildHarvestPrediction = function buildHarvestPrediction(match) {
|
|
436
|
+
return {
|
|
437
|
+
evidenceDepth: {
|
|
438
|
+
totalChars: 0,
|
|
439
|
+
totalTokens: 0,
|
|
440
|
+
verifiedChars: 0,
|
|
441
|
+
verifiedTokens: 0
|
|
442
|
+
},
|
|
443
|
+
evidenceTier: 'session-harvest',
|
|
444
|
+
meanTokenLogProbability: 0,
|
|
445
|
+
poolHeldExtension: false,
|
|
446
|
+
posterior: 0,
|
|
447
|
+
rankScore: 0,
|
|
448
|
+
shortlistSize: 0,
|
|
449
|
+
surface: match.surface,
|
|
450
|
+
termType: 'word',
|
|
451
|
+
text: match.ghostText,
|
|
452
|
+
winnerMargin: 0
|
|
453
|
+
};
|
|
454
|
+
};
|
|
331
455
|
var isEnglishLocale = function isEnglishLocale(locale) {
|
|
332
456
|
if (!locale) {
|
|
333
457
|
return false;
|
|
@@ -339,6 +463,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
339
463
|
var surface = (_options$surface = options === null || options === void 0 ? void 0 : options.surface) !== null && _options$surface !== void 0 ? _options$surface : 'editor';
|
|
340
464
|
var locale = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : typeof navigator !== 'undefined' ? navigator.language : undefined;
|
|
341
465
|
var isAutocompleteEnabled = isEnglishLocale(locale);
|
|
466
|
+
var isInlineCodeHarvestEnabled = isAutocompleteEnabled && (options === null || options === void 0 ? void 0 : options.harvestInlineCode) === true;
|
|
342
467
|
var debounceTimer = null;
|
|
343
468
|
var decisionDeadlineTimer = null;
|
|
344
469
|
var hasIngestedPage = false;
|
|
@@ -381,14 +506,26 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
381
506
|
}
|
|
382
507
|
return options !== null && options !== void 0 && options.useLocalModel ? 'localLlm' : 'server';
|
|
383
508
|
};
|
|
384
|
-
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Which path produced a given suggestion.
|
|
512
|
+
*
|
|
513
|
+
* A harvested surface is reported as `harvest` rather than by the slow-lane
|
|
514
|
+
* state it happened to be shown under, so its views and acceptances stay
|
|
515
|
+
* separable from the scored path's and cannot quietly move the headline
|
|
516
|
+
* acceptance rate.
|
|
517
|
+
*/
|
|
518
|
+
var completionSourceFor = function completionSourceFor(suggestion) {
|
|
519
|
+
return (suggestion === null || suggestion === void 0 ? void 0 : suggestion.evidenceTier) === 'session-harvest' ? 'harvest' : getCompletionSource();
|
|
520
|
+
};
|
|
521
|
+
var fireSuggestionDismissedAnalytics = function fireSuggestionDismissedAnalytics(reason, suggestion) {
|
|
385
522
|
var _api$analytics;
|
|
386
523
|
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
|
|
387
524
|
action: _analytics.ACTION.SUGGESTION_DISMISSED,
|
|
388
525
|
actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
389
526
|
eventType: _analytics.EVENT_TYPE.TRACK,
|
|
390
527
|
attributes: {
|
|
391
|
-
completionSource:
|
|
528
|
+
completionSource: completionSourceFor(suggestion),
|
|
392
529
|
reason: reason,
|
|
393
530
|
surface: surface
|
|
394
531
|
}
|
|
@@ -443,7 +580,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
443
580
|
actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
444
581
|
eventType: _analytics.EVENT_TYPE.TRACK,
|
|
445
582
|
attributes: {
|
|
446
|
-
completionSource:
|
|
583
|
+
completionSource: completionSourceFor(suggestion),
|
|
447
584
|
suggestionLength: suggestionLength,
|
|
448
585
|
typedLength: typedLength,
|
|
449
586
|
kssDelta: kssDelta,
|
|
@@ -509,6 +646,10 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
509
646
|
var registeredSlowLaneStatus = (0, _slowLaneClient.getDefaultSlowLaneClientStatus)();
|
|
510
647
|
(0, _debugMode.ctcTag)('init', "slow lane registered \xB7 id=".concat(slowLaneClientId, " \xB7 selected=").concat(slowLaneClientKind, " \xB7 canonical=").concat(registeredSlowLaneStatus.canonicalScoringSupported ? 'yes' : 'no', " \xB7 ready=").concat((_registeredSlowLaneSt = registeredSlowLaneStatus.localModelReady) !== null && _registeredSlowLaneSt !== void 0 ? _registeredSlowLaneSt : 'n/a'), slowLaneClientKind === 'localLlm' ? _debugMode.CTC_STYLES.good : _debugMode.CTC_STYLES.warn);
|
|
511
648
|
var contextRequestInFlight = false;
|
|
649
|
+
// A read the host asked for while another was open, kept so it can be made
|
|
650
|
+
// once that one settles. One slot, not a queue: a burst of notifications only
|
|
651
|
+
// ever means "read again", and the last of them describes the current state.
|
|
652
|
+
var queuedContextRefreshSource;
|
|
512
653
|
var lastContextRefreshAt = 0;
|
|
513
654
|
// Bounds the word-boundary retry loop so it terminates even when the editor is
|
|
514
655
|
// not in a comment thread (parentCommentContent never resolves).
|
|
@@ -516,7 +657,165 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
516
657
|
// Set when the plugin is torn down so in-flight getContext() resolutions don't
|
|
517
658
|
// mutate the global text-predictor state after destruction.
|
|
518
659
|
var destroyed = false;
|
|
519
|
-
|
|
660
|
+
// L1 ingestion cannot run before the vocabulary has loaded: `incrementSessionFreq`
|
|
661
|
+
// only touches trie nodes that already exist, so an empty trie silently rejects
|
|
662
|
+
// every word. Text that arrives first — which a page usually does, since the
|
|
663
|
+
// chat resolves it on focus in the same tick as the load starts — is held here
|
|
664
|
+
// and applied once the load has settled.
|
|
665
|
+
var pendingSessionContextTexts = new Set();
|
|
666
|
+
var sessionIngestedContextTexts = new Set();
|
|
667
|
+
var isVocabularyReady = false;
|
|
668
|
+
var vocabularyLoadPromise;
|
|
669
|
+
|
|
670
|
+
// Context text waiting on the harvester chunk, the vocabulary, or both. The
|
|
671
|
+
// harvester drops surfaces the vocabulary already holds, so intake before the
|
|
672
|
+
// load has settled would admit ordinary words no one needs completed.
|
|
673
|
+
var pendingHarvestTexts = new Set();
|
|
674
|
+
// Separate from `sessionIngestedContextTexts` because the two consumers settle
|
|
675
|
+
// at different times: reply occurrences accumulate, so text fed twice would
|
|
676
|
+
// count twice.
|
|
677
|
+
var harvestedContextTexts = new Set();
|
|
678
|
+
var inlineCodeHarvester = null;
|
|
679
|
+
var inlineCodeHarvesterPromise;
|
|
680
|
+
var addBoundedContextText = function addBoundedContextText(texts, text) {
|
|
681
|
+
texts.add(text);
|
|
682
|
+
// Evict oldest entries (Set preserves insertion order) to bound memory.
|
|
683
|
+
while (texts.size > MAX_INGESTED_CONTEXT_TEXTS) {
|
|
684
|
+
var oldest = texts.values().next().value;
|
|
685
|
+
if (oldest === undefined) {
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
texts.delete(oldest);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var flushPendingSessionContext = function flushPendingSessionContext() {
|
|
692
|
+
if (!isVocabularyReady || destroyed) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
var _iterator = _createForOfIteratorHelper(pendingSessionContextTexts),
|
|
696
|
+
_step;
|
|
697
|
+
try {
|
|
698
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
699
|
+
var text = _step.value;
|
|
700
|
+
if (!sessionIngestedContextTexts.has(text)) {
|
|
701
|
+
(0, _textPredictor.ingestDocumentPage)(text);
|
|
702
|
+
addBoundedContextText(sessionIngestedContextTexts, text);
|
|
703
|
+
}
|
|
704
|
+
pendingSessionContextTexts.delete(text);
|
|
705
|
+
}
|
|
706
|
+
} catch (err) {
|
|
707
|
+
_iterator.e(err);
|
|
708
|
+
} finally {
|
|
709
|
+
_iterator.f();
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
var flushPendingHarvestTexts = function flushPendingHarvestTexts() {
|
|
713
|
+
var harvester = inlineCodeHarvester;
|
|
714
|
+
if (!harvester || !isVocabularyReady || destroyed || pendingHarvestTexts.size === 0) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
var _iterator2 = _createForOfIteratorHelper(pendingHarvestTexts),
|
|
718
|
+
_step2;
|
|
719
|
+
try {
|
|
720
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
721
|
+
var text = _step2.value;
|
|
722
|
+
if (!harvestedContextTexts.has(text)) {
|
|
723
|
+
harvester.harvestInlineCodeFromText(text);
|
|
724
|
+
addBoundedContextText(harvestedContextTexts, text);
|
|
725
|
+
}
|
|
726
|
+
pendingHarvestTexts.delete(text);
|
|
727
|
+
}
|
|
728
|
+
} catch (err) {
|
|
729
|
+
_iterator2.e(err);
|
|
730
|
+
} finally {
|
|
731
|
+
_iterator2.f();
|
|
732
|
+
}
|
|
733
|
+
harvester.logInlineCodeHarvest('context');
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Load the harvester chunk, once, and only for a host that asked for it.
|
|
738
|
+
*
|
|
739
|
+
* Kept off the critical path in the same way the vocabulary and vector loads
|
|
740
|
+
* are: nothing is requested until the editor is focused, so a session that
|
|
741
|
+
* never types in the chat pays nothing for the feature.
|
|
742
|
+
*/
|
|
743
|
+
var ensureInlineCodeHarvester = function ensureInlineCodeHarvester() {
|
|
744
|
+
if (!isInlineCodeHarvestEnabled || destroyed) {
|
|
745
|
+
return Promise.resolve(null);
|
|
746
|
+
}
|
|
747
|
+
inlineCodeHarvesterPromise !== null && inlineCodeHarvesterPromise !== void 0 ? inlineCodeHarvesterPromise : inlineCodeHarvesterPromise = Promise.resolve().then(function () {
|
|
748
|
+
return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-inline-code-harvester" */
|
|
749
|
+
'./inline-code-harvester'));
|
|
750
|
+
}).then(function (module) {
|
|
751
|
+
if (destroyed) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
inlineCodeHarvester = module;
|
|
755
|
+
loadedInlineCodeHarvester = module;
|
|
756
|
+
flushPendingHarvestTexts();
|
|
757
|
+
return module;
|
|
758
|
+
}).catch(function (error) {
|
|
759
|
+
// Clear the promise so a later focus can retry; the queued text is
|
|
760
|
+
// still held and is fed by whichever attempt succeeds.
|
|
761
|
+
inlineCodeHarvesterPromise = undefined;
|
|
762
|
+
(0, _monitoring.logException)(error, {
|
|
763
|
+
location: 'editor-plugin-autocomplete/loadInlineCodeHarvester'
|
|
764
|
+
});
|
|
765
|
+
return null;
|
|
766
|
+
});
|
|
767
|
+
return inlineCodeHarvesterPromise;
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Re-read the document's code-marked spans.
|
|
772
|
+
*
|
|
773
|
+
* The backtick input rule consumes both delimiters on the closing tick, so a
|
|
774
|
+
* finished span in the live document is only findable through its mark — and
|
|
775
|
+
* only by walking, since a mark carries no notification.
|
|
776
|
+
*/
|
|
777
|
+
var harvestDocument = function harvestDocument(doc, trigger) {
|
|
778
|
+
if (!isInlineCodeHarvestEnabled || destroyed) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
var harvester = inlineCodeHarvester;
|
|
782
|
+
if (!harvester) {
|
|
783
|
+
void ensureInlineCodeHarvester();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (!isVocabularyReady) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
harvester.harvestInlineCodeFromDoc(doc);
|
|
790
|
+
harvester.logInlineCodeHarvest(trigger);
|
|
791
|
+
};
|
|
792
|
+
var ensureVocabularyReady = function ensureVocabularyReady() {
|
|
793
|
+
var _options$useLocalMode;
|
|
794
|
+
if (isVocabularyReady) {
|
|
795
|
+
flushPendingSessionContext();
|
|
796
|
+
flushPendingHarvestTexts();
|
|
797
|
+
return Promise.resolve();
|
|
798
|
+
}
|
|
799
|
+
vocabularyLoadPromise !== null && vocabularyLoadPromise !== void 0 ? vocabularyLoadPromise : vocabularyLoadPromise = (0, _textPredictor.loadDefaultVocabulary)({
|
|
800
|
+
isLocalLLM: (_options$useLocalMode = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode !== void 0 ? _options$useLocalMode : false,
|
|
801
|
+
surface: surface
|
|
802
|
+
}).then(function () {
|
|
803
|
+
if (destroyed) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
isVocabularyReady = true;
|
|
807
|
+
flushPendingSessionContext();
|
|
808
|
+
flushPendingHarvestTexts();
|
|
809
|
+
}).catch(function (error) {
|
|
810
|
+
// Do not consume the pending text on failure. A later focus retries the
|
|
811
|
+
// load and can still apply the original context exactly once.
|
|
812
|
+
vocabularyLoadPromise = undefined;
|
|
813
|
+
(0, _monitoring.logException)(error, {
|
|
814
|
+
location: 'editor-plugin-autocomplete/loadDefaultVocabulary'
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
return vocabularyLoadPromise;
|
|
818
|
+
};
|
|
520
819
|
var logContextResolved = function logContextResolved(source, context) {
|
|
521
820
|
var _context$parentCommen, _context$siblingComme4, _context$siblingComme5;
|
|
522
821
|
if (!(0, _debugMode.isAutocompleteDebugEnabled)()) {
|
|
@@ -526,14 +825,134 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
526
825
|
// eslint-disable-next-line no-console
|
|
527
826
|
console.log('%c[CTC:signal]%c getContext resolved', _debugMode.CTC_STYLES.brand, _debugMode.CTC_STYLES.body, {
|
|
528
827
|
source: source,
|
|
828
|
+
scope: context === null || context === void 0 ? void 0 : context.contextScopeKey,
|
|
529
829
|
hasParentComment: !!(context !== null && context !== void 0 && context.parentCommentContent),
|
|
530
830
|
parentCommentPreview: context === null || context === void 0 || (_context$parentCommen = context.parentCommentContent) === null || _context$parentCommen === void 0 ? void 0 : _context$parentCommen.slice(0, 80),
|
|
531
831
|
siblingCount: (_context$siblingComme4 = context === null || context === void 0 || (_context$siblingComme5 = context.siblingCommentsContents) === null || _context$siblingComme5 === void 0 ? void 0 : _context$siblingComme5.length) !== null && _context$siblingComme4 !== void 0 ? _context$siblingComme4 : 0,
|
|
532
832
|
hasFullPage: !!(context !== null && context !== void 0 && context.fullPageContent)
|
|
533
833
|
});
|
|
534
834
|
};
|
|
835
|
+
var ingestContextText = function ingestContextText(text) {
|
|
836
|
+
if (!text) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (!sessionIngestedContextTexts.has(text)) {
|
|
840
|
+
addBoundedContextText(pendingSessionContextTexts, text);
|
|
841
|
+
}
|
|
842
|
+
// Queued unconditionally rather than only once the chunk is present:
|
|
843
|
+
// context usually resolves in the same tick the load starts, and text
|
|
844
|
+
// dropped for arriving early is a reply that can never be harvested.
|
|
845
|
+
if (isInlineCodeHarvestEnabled && !harvestedContextTexts.has(text)) {
|
|
846
|
+
addBoundedContextText(pendingHarvestTexts, text);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
var ingestResolvedContext = function ingestResolvedContext() {
|
|
850
|
+
var _resolvedContext, _resolvedContext2, _resolvedContext$sibl, _resolvedContext3;
|
|
851
|
+
ingestContextText((_resolvedContext = resolvedContext) === null || _resolvedContext === void 0 ? void 0 : _resolvedContext.fullPageContent);
|
|
852
|
+
ingestContextText((_resolvedContext2 = resolvedContext) === null || _resolvedContext2 === void 0 ? void 0 : _resolvedContext2.parentCommentContent);
|
|
853
|
+
var _iterator3 = _createForOfIteratorHelper((_resolvedContext$sibl = (_resolvedContext3 = resolvedContext) === null || _resolvedContext3 === void 0 ? void 0 : _resolvedContext3.siblingCommentsContents) !== null && _resolvedContext$sibl !== void 0 ? _resolvedContext$sibl : []),
|
|
854
|
+
_step3;
|
|
855
|
+
try {
|
|
856
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
857
|
+
var siblingCommentContent = _step3.value;
|
|
858
|
+
ingestContextText(siblingCommentContent);
|
|
859
|
+
}
|
|
860
|
+
} catch (err) {
|
|
861
|
+
_iterator3.e(err);
|
|
862
|
+
} finally {
|
|
863
|
+
_iterator3.f();
|
|
864
|
+
}
|
|
865
|
+
flushPendingSessionContext();
|
|
866
|
+
flushPendingHarvestTexts();
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Put back what this editor had contributed to the pot another editor just
|
|
871
|
+
* emptied.
|
|
872
|
+
*
|
|
873
|
+
* The scope that ended belongs to the host that reported it, not to everyone
|
|
874
|
+
* sharing these globals. Without this an editor alongside it — a comment box
|
|
875
|
+
* under a chat panel that switched conversation — is left with none of its
|
|
876
|
+
* learning and no way back to it: the dedupe sets below are per editor, so
|
|
877
|
+
* its own page reads as already ingested and is skipped from then on.
|
|
878
|
+
*
|
|
879
|
+
* Nothing is re-fetched. The context this editor resolved is still held, and
|
|
880
|
+
* what it describes has not changed just because another host moved on.
|
|
881
|
+
*
|
|
882
|
+
* Only for an editor that reported no scope. One that did is party to the
|
|
883
|
+
* same scope system and the scope just dropped may well be its own: a
|
|
884
|
+
* Confluence page transition mounts the incoming editor before tearing down
|
|
885
|
+
* the outgoing one, and the outgoing one is still holding the page that was
|
|
886
|
+
* left. Putting that back is exactly what the eviction was for.
|
|
887
|
+
*/
|
|
888
|
+
var reprimeSessionLearning = function reprimeSessionLearning() {
|
|
889
|
+
var _resolvedContext4;
|
|
890
|
+
if (destroyed || ((_resolvedContext4 = resolvedContext) === null || _resolvedContext4 === void 0 ? void 0 : _resolvedContext4.contextScopeKey) !== undefined) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
pendingSessionContextTexts.clear();
|
|
894
|
+
sessionIngestedContextTexts.clear();
|
|
895
|
+
pendingHarvestTexts.clear();
|
|
896
|
+
harvestedContextTexts.clear();
|
|
897
|
+
ingestResolvedContext();
|
|
898
|
+
if (currentView) {
|
|
899
|
+
harvestDocument(currentView.state.doc, 'reprime');
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Drop everything learned for the scope that just ended.
|
|
905
|
+
*
|
|
906
|
+
* Both stores are claims about what is being discussed, and neither survives
|
|
907
|
+
* the discussion changing: L1 boosts would keep a page's words ranked above
|
|
908
|
+
* the next page's, and the harvested set holds one conversation's content.
|
|
909
|
+
* The dedupe sets go too, so the context that arrives next is treated as
|
|
910
|
+
* unseen and re-primes both — which is what makes this safe to do on a page
|
|
911
|
+
* change, since the transcript is re-ingested along with the new page.
|
|
912
|
+
*
|
|
913
|
+
* The stores are shared, so every other mounted editor is put back in the
|
|
914
|
+
* same tick from context it already holds. Only the scope that ended is
|
|
915
|
+
* actually dropped.
|
|
916
|
+
*/
|
|
917
|
+
var resetSessionScopedLearning = function resetSessionScopedLearning(reason) {
|
|
918
|
+
var _loadedInlineCodeHarv;
|
|
919
|
+
(0, _textPredictor.resetSessionBoosts)();
|
|
920
|
+
(_loadedInlineCodeHarv = loadedInlineCodeHarvester) === null || _loadedInlineCodeHarv === void 0 || _loadedInlineCodeHarv.resetInlineCodeHarvest();
|
|
921
|
+
resolvedContext = undefined;
|
|
922
|
+
pendingSessionContextTexts.clear();
|
|
923
|
+
sessionIngestedContextTexts.clear();
|
|
924
|
+
pendingHarvestTexts.clear();
|
|
925
|
+
harvestedContextTexts.clear();
|
|
926
|
+
(0, _debugMode.ctcTag)('init', "session-scoped learning reset \xB7 ".concat(reason), _debugMode.CTC_STYLES.dim);
|
|
927
|
+
var _iterator4 = _createForOfIteratorHelper(mountedEditors),
|
|
928
|
+
_step4;
|
|
929
|
+
try {
|
|
930
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
931
|
+
var reprimeOther = _step4.value;
|
|
932
|
+
if (reprimeOther !== reprimeSessionLearning) {
|
|
933
|
+
reprimeOther();
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
} catch (err) {
|
|
937
|
+
_iterator4.e(err);
|
|
938
|
+
} finally {
|
|
939
|
+
_iterator4.f();
|
|
940
|
+
}
|
|
941
|
+
};
|
|
535
942
|
var applyContext = function applyContext(context) {
|
|
536
|
-
|
|
943
|
+
// A host that scopes its context tells us which scope each read belongs to.
|
|
944
|
+
// A read with no recorded key to compare against only records: that is the
|
|
945
|
+
// first since the last editor went away, so there is nothing left to throw
|
|
946
|
+
// away and resetting would drop the priming focus just started. A read from
|
|
947
|
+
// a newly mounted editor is not that case — the key outlives the editor
|
|
948
|
+
// precisely so a host that remounts across a navigation still evicts.
|
|
949
|
+
if (context.contextScopeKey !== undefined && context.contextScopeKey !== contextScopeKey) {
|
|
950
|
+
if (contextScopeKey !== undefined) {
|
|
951
|
+
resetSessionScopedLearning("scope ".concat(contextScopeKey, " \u2192 ").concat(context.contextScopeKey));
|
|
952
|
+
}
|
|
953
|
+
contextScopeKey = context.contextScopeKey;
|
|
954
|
+
}
|
|
955
|
+
|
|
537
956
|
// Merge rather than replace: the word-boundary retry may resolve only a
|
|
538
957
|
// late-arriving field (e.g. parentCommentContent) without re-sending
|
|
539
958
|
// fullPageContent, so replacing would drop previously resolved context.
|
|
@@ -545,38 +964,10 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
545
964
|
return value !== undefined;
|
|
546
965
|
}));
|
|
547
966
|
resolvedContext = _objectSpread(_objectSpread({}, resolvedContext), definedContext);
|
|
548
|
-
|
|
549
|
-
if (!text || ingestedContextTexts.has(text)) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
ingestedContextTexts.add(text);
|
|
553
|
-
// Evict oldest entries (Set preserves insertion order) to bound memory.
|
|
554
|
-
while (ingestedContextTexts.size > MAX_INGESTED_CONTEXT_TEXTS) {
|
|
555
|
-
var oldest = ingestedContextTexts.values().next().value;
|
|
556
|
-
if (oldest === undefined) {
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
ingestedContextTexts.delete(oldest);
|
|
560
|
-
}
|
|
561
|
-
(0, _textPredictor.ingestDocumentPage)(text);
|
|
562
|
-
};
|
|
563
|
-
ingestContextText(context.fullPageContent);
|
|
564
|
-
ingestContextText(context.parentCommentContent);
|
|
565
|
-
var _iterator = _createForOfIteratorHelper((_context$siblingComme6 = context.siblingCommentsContents) !== null && _context$siblingComme6 !== void 0 ? _context$siblingComme6 : []),
|
|
566
|
-
_step;
|
|
567
|
-
try {
|
|
568
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
569
|
-
var siblingCommentContent = _step.value;
|
|
570
|
-
ingestContextText(siblingCommentContent);
|
|
571
|
-
}
|
|
967
|
+
ingestResolvedContext();
|
|
572
968
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} catch (err) {
|
|
576
|
-
_iterator.e(err);
|
|
577
|
-
} finally {
|
|
578
|
-
_iterator.f();
|
|
579
|
-
}
|
|
969
|
+
// Context arrived after word boundaries may already have fired. Re-send
|
|
970
|
+
// slow-lane context immediately so the next inference includes the thread.
|
|
580
971
|
if (currentView) {
|
|
581
972
|
slowLaneClient.updateContext(buildSlowLaneText(currentView.state.doc.textContent, resolvedContext));
|
|
582
973
|
}
|
|
@@ -587,11 +978,23 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
587
978
|
* (no getContext, a request already in flight, or throttled). Callers that
|
|
588
979
|
* track a retry budget should only count attempts where this returns true.
|
|
589
980
|
*/
|
|
590
|
-
var
|
|
981
|
+
var _refreshContext = function refreshContext(_ref5) {
|
|
591
982
|
var source = _ref5.source,
|
|
592
983
|
_ref5$allowThrottle = _ref5.allowThrottle,
|
|
593
984
|
allowThrottle = _ref5$allowThrottle === void 0 ? true : _ref5$allowThrottle;
|
|
594
|
-
if (!(options !== null && options !== void 0 && options.getContext)
|
|
985
|
+
if (!(options !== null && options !== void 0 && options.getContext)) {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
if (contextRequestInFlight) {
|
|
989
|
+
// Only the reads that bypass the throttle are worth keeping: those are
|
|
990
|
+
// the ones the host asked for by name, and a scope change travels among
|
|
991
|
+
// them with no second channel to arrive on, so losing one would strand
|
|
992
|
+
// the eviction until something else happened to move. A word-boundary
|
|
993
|
+
// poll is a retry loop for context that has not landed yet, and the read
|
|
994
|
+
// already open will bring it.
|
|
995
|
+
if (!allowThrottle) {
|
|
996
|
+
queuedContextRefreshSource = source;
|
|
997
|
+
}
|
|
595
998
|
return false;
|
|
596
999
|
}
|
|
597
1000
|
var now = Date.now();
|
|
@@ -618,6 +1021,14 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
618
1021
|
});
|
|
619
1022
|
}).finally(function () {
|
|
620
1023
|
contextRequestInFlight = false;
|
|
1024
|
+
var queuedSource = queuedContextRefreshSource;
|
|
1025
|
+
queuedContextRefreshSource = undefined;
|
|
1026
|
+
if (queuedSource !== undefined && !destroyed) {
|
|
1027
|
+
_refreshContext({
|
|
1028
|
+
source: queuedSource,
|
|
1029
|
+
allowThrottle: false
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
621
1032
|
});
|
|
622
1033
|
return true;
|
|
623
1034
|
};
|
|
@@ -650,6 +1061,48 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
650
1061
|
}
|
|
651
1062
|
cancelActiveDecision();
|
|
652
1063
|
};
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Offer a harvested inline-code surface on a prefix the scored path left
|
|
1067
|
+
* empty. Returns whether one was committed.
|
|
1068
|
+
*/
|
|
1069
|
+
var commitHarvestedSuggestion = function commitHarvestedSuggestion(view, decision) {
|
|
1070
|
+
var harvester = inlineCodeHarvester;
|
|
1071
|
+
if (!harvester || !isInlineCodeHarvestEnabled) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
if (!scoredPathFinishedEmpty(decision.textBefore)) {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
var typedPrefix = getTrailingSurfaceToken(decision.textBefore);
|
|
1078
|
+
var match = harvester.findHarvestedCompletion(typedPrefix);
|
|
1079
|
+
if (!match) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
if ((0, _textPredictor.isSurfaceInAcceptCooldown)(match.surface) || repeatsPrecedingText(match, decision.textBefore)) {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
var suggestion = showGhostText(view, buildHarvestPrediction(match), decision.position, decision.revision, decision.startedAt, match.typedPrefixLength);
|
|
1086
|
+
if (!suggestion) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
cancelActiveDecision();
|
|
1090
|
+
(0, _debugMode.ctcTag)('harvest', "offered \"".concat(match.surface, "\" for \"").concat(typedPrefix, "\" \xB7 ").concat(match.collapsedBy, " \xB7 ").concat(match.replyOccurrences, " reply / ").concat(match.documentSpans, " doc sightings").concat(match.rivalSurfaces.length > 0 ? " \xB7 over ".concat(match.rivalSurfaces.join(', ')) : ''), _debugMode.CTC_STYLES.lm);
|
|
1091
|
+
if (suggestion.ghostText !== lastShownGhostText) {
|
|
1092
|
+
var _api$analytics5;
|
|
1093
|
+
lastShownGhostText = suggestion.ghostText;
|
|
1094
|
+
api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 || _api$analytics5.actions.fireAnalyticsEvent({
|
|
1095
|
+
action: _analytics.ACTION.SUGGESTION_VIEWED,
|
|
1096
|
+
actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
1097
|
+
eventType: _analytics.EVENT_TYPE.TRACK,
|
|
1098
|
+
attributes: {
|
|
1099
|
+
completionSource: completionSourceFor(suggestion),
|
|
1100
|
+
surface: surface
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
return true;
|
|
1105
|
+
};
|
|
653
1106
|
var evaluateActiveDecision = function evaluateActiveDecision(view, revision) {
|
|
654
1107
|
var decision = activeDecision;
|
|
655
1108
|
if (!decision || decision.revision !== revision || !isDecisionCurrent(view, decision)) {
|
|
@@ -686,8 +1139,11 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
686
1139
|
}
|
|
687
1140
|
var prediction = (0, _textPredictor.predict)(decision.textBefore);
|
|
688
1141
|
if (!prediction || prediction.text.length === 0) {
|
|
689
|
-
//
|
|
690
|
-
//
|
|
1142
|
+
// Only where the scored path has finished and come away with nothing
|
|
1143
|
+
// does a harvested surface get to answer; otherwise remain in
|
|
1144
|
+
// `collecting` until an async signal arrives or the hard deadline
|
|
1145
|
+
// expires. We never display a provisional fallback.
|
|
1146
|
+
commitHarvestedSuggestion(view, decision);
|
|
691
1147
|
return;
|
|
692
1148
|
}
|
|
693
1149
|
var readyAtMs = performance.now() - decision.startedAt;
|
|
@@ -727,14 +1183,14 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
727
1183
|
});
|
|
728
1184
|
}
|
|
729
1185
|
if (suggestion.ghostText !== lastShownGhostText) {
|
|
730
|
-
var _api$
|
|
1186
|
+
var _api$analytics6;
|
|
731
1187
|
lastShownGhostText = suggestion.ghostText;
|
|
732
|
-
api === null || api === void 0 || (_api$
|
|
1188
|
+
api === null || api === void 0 || (_api$analytics6 = api.analytics) === null || _api$analytics6 === void 0 || _api$analytics6.actions.fireAnalyticsEvent({
|
|
733
1189
|
action: _analytics.ACTION.SUGGESTION_VIEWED,
|
|
734
1190
|
actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
735
1191
|
eventType: _analytics.EVENT_TYPE.TRACK,
|
|
736
1192
|
attributes: {
|
|
737
|
-
completionSource:
|
|
1193
|
+
completionSource: completionSourceFor(suggestion),
|
|
738
1194
|
surface: surface
|
|
739
1195
|
}
|
|
740
1196
|
});
|
|
@@ -918,11 +1374,13 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
918
1374
|
});
|
|
919
1375
|
},
|
|
920
1376
|
Escape: function Escape(state, dispatch) {
|
|
1377
|
+
var _autocompletePluginKe;
|
|
1378
|
+
var dismissed = (_autocompletePluginKe = autocompletePluginKey.getState(state)) === null || _autocompletePluginKe === void 0 ? void 0 : _autocompletePluginKe.suggestion;
|
|
921
1379
|
var didClear = clearGhostText(state, dispatch);
|
|
922
1380
|
if (didClear) {
|
|
923
1381
|
cancelActiveDecision();
|
|
924
1382
|
dismissedContext = getTextBeforeCursor(state);
|
|
925
|
-
fireSuggestionDismissedAnalytics('escape');
|
|
1383
|
+
fireSuggestionDismissedAnalytics('escape', dismissed !== null && dismissed !== void 0 ? dismissed : null);
|
|
926
1384
|
}
|
|
927
1385
|
return didClear;
|
|
928
1386
|
}
|
|
@@ -936,7 +1394,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
936
1394
|
if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
|
|
937
1395
|
clearGhostText(view.state, view.dispatch);
|
|
938
1396
|
cancelActiveDecision();
|
|
939
|
-
fireSuggestionDismissedAnalytics('blur');
|
|
1397
|
+
fireSuggestionDismissedAnalytics('blur', pluginState.suggestion);
|
|
940
1398
|
}
|
|
941
1399
|
return false;
|
|
942
1400
|
},
|
|
@@ -945,12 +1403,14 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
945
1403
|
return false;
|
|
946
1404
|
}
|
|
947
1405
|
if (!event.target.closest('[data-autocomplete-ghost="true"]')) {
|
|
1406
|
+
var _autocompletePluginKe2;
|
|
1407
|
+
var dismissed = (_autocompletePluginKe2 = autocompletePluginKey.getState(view.state)) === null || _autocompletePluginKe2 === void 0 ? void 0 : _autocompletePluginKe2.suggestion;
|
|
948
1408
|
var didClear = clearGhostText(view.state, view.dispatch);
|
|
949
1409
|
if (didClear) {
|
|
950
1410
|
cancelActiveDecision();
|
|
951
1411
|
dismissedContext = getTextBeforeCursor(view.state);
|
|
952
1412
|
lastShownGhostText = '';
|
|
953
|
-
fireSuggestionDismissedAnalytics('click');
|
|
1413
|
+
fireSuggestionDismissedAnalytics('click', dismissed !== null && dismissed !== void 0 ? dismissed : null);
|
|
954
1414
|
}
|
|
955
1415
|
return false;
|
|
956
1416
|
}
|
|
@@ -966,17 +1426,19 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
966
1426
|
}
|
|
967
1427
|
return accepted;
|
|
968
1428
|
},
|
|
969
|
-
focus: function focus() {
|
|
970
|
-
var _options$
|
|
1429
|
+
focus: function focus(view) {
|
|
1430
|
+
var _options$useLocalMode2, _options$useLocalMode3;
|
|
971
1431
|
if (!isAutocompleteEnabled) {
|
|
972
1432
|
return false;
|
|
973
1433
|
}
|
|
974
|
-
(
|
|
975
|
-
|
|
976
|
-
|
|
1434
|
+
void ensureVocabularyReady();
|
|
1435
|
+
// First point at which the session is known to be using the input,
|
|
1436
|
+
// which is where the rest of the artifacts are requested too.
|
|
1437
|
+
void ensureInlineCodeHarvester().then(function () {
|
|
1438
|
+
return harvestDocument(view.state.doc, 'focus');
|
|
977
1439
|
}).catch(function (error) {
|
|
978
1440
|
(0, _monitoring.logException)(error, {
|
|
979
|
-
location: 'editor-plugin-autocomplete/
|
|
1441
|
+
location: 'editor-plugin-autocomplete/harvestDocument'
|
|
980
1442
|
});
|
|
981
1443
|
});
|
|
982
1444
|
(0, _textPredictor.loadVectorsAsync)({
|
|
@@ -997,7 +1459,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
997
1459
|
});
|
|
998
1460
|
if (!hasIngestedPage) {
|
|
999
1461
|
hasIngestedPage = true;
|
|
1000
|
-
|
|
1462
|
+
_refreshContext({
|
|
1001
1463
|
source: 'focus',
|
|
1002
1464
|
allowThrottle: false
|
|
1003
1465
|
});
|
|
@@ -1010,12 +1472,14 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
1010
1472
|
// Capture up front so a subscription notification before the first PM
|
|
1011
1473
|
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
1012
1474
|
currentView = editorView;
|
|
1475
|
+
mountedEditors.add(reprimeSessionLearning);
|
|
1013
1476
|
|
|
1014
1477
|
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
1015
1478
|
if (isAutocompleteEnabled && options !== null && options !== void 0 && options.subscribeToContextUpdates) {
|
|
1016
1479
|
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(function () {
|
|
1017
|
-
// Bypass throttle for freshness;
|
|
1018
|
-
|
|
1480
|
+
// Bypass throttle for freshness; a read still open defers this one
|
|
1481
|
+
// rather than overlapping it.
|
|
1482
|
+
_refreshContext({
|
|
1019
1483
|
source: 'subscription',
|
|
1020
1484
|
allowThrottle: false
|
|
1021
1485
|
});
|
|
@@ -1040,19 +1504,24 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
1040
1504
|
maybeUpdateSessionFrequency(view, prevState);
|
|
1041
1505
|
var textBefore = getTextBeforeCursor(view.state);
|
|
1042
1506
|
if ((0, _slowLaneClient.isWordBoundary)(textBefore)) {
|
|
1043
|
-
var
|
|
1507
|
+
var _resolvedContext5;
|
|
1044
1508
|
slowLaneClient.updateContext(buildSlowLaneText(view.state.doc.textContent, resolvedContext));
|
|
1045
1509
|
|
|
1510
|
+
// The backtick input rule has fired by the time a span is
|
|
1511
|
+
// finished, so a word boundary is the earliest point the mark
|
|
1512
|
+
// exists to be found.
|
|
1513
|
+
harvestDocument(view.state.doc, 'word-boundary');
|
|
1514
|
+
|
|
1046
1515
|
// Context may not have resolved on first focus (e.g. comment
|
|
1047
1516
|
// thread still loading). Retry on word boundaries until we have
|
|
1048
1517
|
// the parent comment, throttled so we don't refetch constantly
|
|
1049
1518
|
// and capped so non-comment editors stop retrying entirely.
|
|
1050
1519
|
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
1051
1520
|
// never grow parentCommentContent, so polling only burns the budget.
|
|
1052
|
-
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((
|
|
1521
|
+
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((_resolvedContext5 = resolvedContext) !== null && _resolvedContext5 !== void 0 && _resolvedContext5.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
1053
1522
|
// Only count the attempt when a fetch actually started, so an
|
|
1054
1523
|
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
1055
|
-
if (
|
|
1524
|
+
if (_refreshContext({
|
|
1056
1525
|
source: 'word-boundary'
|
|
1057
1526
|
})) {
|
|
1058
1527
|
wordBoundaryRefreshAttempts++;
|
|
@@ -1076,7 +1545,35 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
1076
1545
|
if (hasDestroy(slowLaneClient)) {
|
|
1077
1546
|
slowLaneClient.destroy();
|
|
1078
1547
|
}
|
|
1079
|
-
|
|
1548
|
+
queuedContextRefreshSource = undefined;
|
|
1549
|
+
pendingSessionContextTexts.clear();
|
|
1550
|
+
sessionIngestedContextTexts.clear();
|
|
1551
|
+
pendingHarvestTexts.clear();
|
|
1552
|
+
harvestedContextTexts.clear();
|
|
1553
|
+
// The harvested set holds one conversation's user and assistant
|
|
1554
|
+
// content, and the L1 boosts describe what that conversation was
|
|
1555
|
+
// about. Both live at module scope, so they have to be emptied here
|
|
1556
|
+
// or they leak into whichever editor mounts next — but only once no
|
|
1557
|
+
// editor is left to be using them. The scope key goes with them, and
|
|
1558
|
+
// only with them: clearing it while another editor is still mounted
|
|
1559
|
+
// would leave that editor's next read looking like a first one, with
|
|
1560
|
+
// nothing to compare against and everything still loaded.
|
|
1561
|
+
mountedEditors.delete(reprimeSessionLearning);
|
|
1562
|
+
if (mountedEditors.size === 0) {
|
|
1563
|
+
var _loadedInlineCodeHarv2;
|
|
1564
|
+
(_loadedInlineCodeHarv2 = loadedInlineCodeHarvester) === null || _loadedInlineCodeHarv2 === void 0 || _loadedInlineCodeHarv2.resetInlineCodeHarvest();
|
|
1565
|
+
// Only for a host that scopes its context, which is the one asking
|
|
1566
|
+
// for its learning to end with the conversation. A host that sends
|
|
1567
|
+
// no scope has boosts belonging to the page it is on, and that page
|
|
1568
|
+
// outlives an editor being unmounted — a Confluence comment box is
|
|
1569
|
+
// closed far more often than the page under it changes.
|
|
1570
|
+
if (contextScopeKey !== undefined) {
|
|
1571
|
+
(0, _textPredictor.resetSessionBoosts)();
|
|
1572
|
+
contextScopeKey = undefined;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
inlineCodeHarvester = null;
|
|
1576
|
+
inlineCodeHarvesterPromise = undefined;
|
|
1080
1577
|
}
|
|
1081
1578
|
};
|
|
1082
1579
|
}
|