@atlaskit/editor-plugin-autocomplete 2.0.0 → 2.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 +22 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/autocompletePlugin.js +3 -2
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +32 -3
- package/dist/cjs/pm-plugins/scoring-pipeline.js +43 -17
- package/dist/cjs/pm-plugins/text-predictor.js +27 -13
- package/dist/es2019/autocompletePlugin.js +3 -2
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +32 -3
- package/dist/es2019/pm-plugins/scoring-pipeline.js +32 -14
- package/dist/es2019/pm-plugins/text-predictor.js +18 -8
- package/dist/esm/autocompletePlugin.js +3 -2
- package/dist/esm/pm-plugins/autocomplete-plugin.js +32 -3
- package/dist/esm/pm-plugins/scoring-pipeline.js +42 -17
- package/dist/esm/pm-plugins/text-predictor.js +28 -14
- package/dist/types/autocompletePluginType.d.ts +3 -1
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +3 -1
- package/dist/types/pm-plugins/scoring-pipeline.d.ts +10 -0
- package/dist/types-ts4.5/autocompletePluginType.d.ts +5 -1
- package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +3 -1
- package/dist/types-ts4.5/pm-plugins/scoring-pipeline.d.ts +10 -0
- package/package.json +3 -2
- package/src/autocompletePlugin.tsx +2 -2
- package/src/autocompletePluginType.ts +3 -1
- package/src/pm-plugins/autocomplete-plugin.ts +33 -3
- package/src/pm-plugins/scoring-pipeline.ts +41 -15
- package/src/pm-plugins/text-predictor.ts +26 -8
- package/tsconfig.app.json +3 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
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
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; }
|
|
4
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
4
5
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
5
6
|
import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
|
|
6
7
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
@@ -61,6 +62,7 @@ var setAutocompleteMeta = function setAutocompleteMeta(tr, meta) {
|
|
|
61
62
|
/**
|
|
62
63
|
* Apply a ghost text suggestion to the editor state.
|
|
63
64
|
*/
|
|
65
|
+
var lastShownGhostText = '';
|
|
64
66
|
var showGhostText = function showGhostText(view, text, position) {
|
|
65
67
|
var state = view.state,
|
|
66
68
|
dispatch = view.dispatch;
|
|
@@ -139,7 +141,7 @@ var buildSlowLaneText = function buildSlowLaneText(docText, context) {
|
|
|
139
141
|
lines.push("reply ".concat(nextReplyNumber, ": ").concat(docText));
|
|
140
142
|
return lines.join('\n');
|
|
141
143
|
};
|
|
142
|
-
export var createAutocompletePlugin = function createAutocompletePlugin(options) {
|
|
144
|
+
export var createAutocompletePlugin = function createAutocompletePlugin(options, api) {
|
|
143
145
|
var debounceTimer = null;
|
|
144
146
|
var hasIngestedPage = false;
|
|
145
147
|
var resolvedContext;
|
|
@@ -197,6 +199,15 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options)
|
|
|
197
199
|
var prediction = predict(textBefore);
|
|
198
200
|
if (prediction && prediction.length > 0) {
|
|
199
201
|
showGhostText(view, prediction, selection.from);
|
|
202
|
+
if (prediction !== lastShownGhostText) {
|
|
203
|
+
var _api$analytics;
|
|
204
|
+
lastShownGhostText = prediction;
|
|
205
|
+
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
|
|
206
|
+
action: ACTION.SUGGESTION_VIEWED,
|
|
207
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
208
|
+
eventType: EVENT_TYPE.TRACK
|
|
209
|
+
});
|
|
210
|
+
}
|
|
200
211
|
}
|
|
201
212
|
}, DEBOUNCE_MS);
|
|
202
213
|
};
|
|
@@ -268,12 +279,30 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options)
|
|
|
268
279
|
handleKeyDown: keydownHandler({
|
|
269
280
|
Tab: function Tab(state, dispatch) {
|
|
270
281
|
var accepted = acceptGhostText(state, dispatch);
|
|
271
|
-
if (accepted)
|
|
282
|
+
if (accepted) {
|
|
283
|
+
var _api$analytics2;
|
|
284
|
+
justAccepted = true;
|
|
285
|
+
lastShownGhostText = '';
|
|
286
|
+
api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
|
|
287
|
+
action: ACTION.SUGGESTION_INSERTED,
|
|
288
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
289
|
+
eventType: EVENT_TYPE.TRACK
|
|
290
|
+
});
|
|
291
|
+
}
|
|
272
292
|
return accepted;
|
|
273
293
|
},
|
|
274
294
|
ArrowRight: function ArrowRight(state, dispatch) {
|
|
275
295
|
var accepted = acceptGhostText(state, dispatch);
|
|
276
|
-
if (accepted)
|
|
296
|
+
if (accepted) {
|
|
297
|
+
var _api$analytics3;
|
|
298
|
+
justAccepted = true;
|
|
299
|
+
lastShownGhostText = '';
|
|
300
|
+
api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.fireAnalyticsEvent({
|
|
301
|
+
action: ACTION.SUGGESTION_INSERTED,
|
|
302
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
303
|
+
eventType: EVENT_TYPE.TRACK
|
|
304
|
+
});
|
|
305
|
+
}
|
|
277
306
|
return accepted;
|
|
278
307
|
},
|
|
279
308
|
Escape: function Escape(state, dispatch) {
|
|
@@ -22,10 +22,15 @@ import grammarTransitionsData from './data/grammar_transitions_10k.json';
|
|
|
22
22
|
var ALPHA = 0.5;
|
|
23
23
|
var BETA = 0.5;
|
|
24
24
|
var NEUTRAL_SCORE = 0.5;
|
|
25
|
-
var STAGE1_WEIGHT = 0.
|
|
26
|
-
var STAGE2_WEIGHT = 0.
|
|
27
|
-
var MIN_STAGE1_SCORE = 0.35;
|
|
25
|
+
export var STAGE1_WEIGHT = 0.35;
|
|
26
|
+
export var STAGE2_WEIGHT = 0.65;
|
|
27
|
+
export var MIN_STAGE1_SCORE = 0.35;
|
|
28
28
|
var L1_SESSION_CAP = 1.2;
|
|
29
|
+
// Minimum prefix-payload max LM probability before Stage 2 activates.
|
|
30
|
+
// Below this threshold the LM signal is too weak to suppress Stage 1 — finalScore
|
|
31
|
+
// falls back to stage1Score directly. Prevents weak prefixes (e.g. "ins" → "instances"
|
|
32
|
+
// at 0.00024) from triggering re-ranking.
|
|
33
|
+
var LM_GATE_THRESHOLD = 0.0005;
|
|
29
34
|
|
|
30
35
|
// ─── Grammar Data (loaded once on import) ───────────────────
|
|
31
36
|
|
|
@@ -189,6 +194,7 @@ function getLmScore(word, lmLogits) {
|
|
|
189
194
|
// ─── Public API ─────────────────────────────────────────────
|
|
190
195
|
|
|
191
196
|
export function rankCandidates(candidates, contextVector, getWordVector, lmLogits, maxTenantFreq, previousWord) {
|
|
197
|
+
var _grammarMeta$dropped;
|
|
192
198
|
// Stage 1
|
|
193
199
|
var stage1Results = candidates.map(function (candidate) {
|
|
194
200
|
var _scoreStage = scoreStage1(candidate, contextVector, getWordVector, maxTenantFreq),
|
|
@@ -202,33 +208,48 @@ export function rankCandidates(candidates, contextVector, getWordVector, lmLogit
|
|
|
202
208
|
stage1Score: stage1Score
|
|
203
209
|
};
|
|
204
210
|
});
|
|
205
|
-
var stage1Survivors =
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
var stage1Survivors = [];
|
|
212
|
+
var stage1Rejected = [];
|
|
213
|
+
var _iterator3 = _createForOfIteratorHelper(stage1Results),
|
|
214
|
+
_step3;
|
|
215
|
+
try {
|
|
216
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
217
|
+
var entry = _step3.value;
|
|
218
|
+
if (entry.stage1Score >= MIN_STAGE1_SCORE) {
|
|
219
|
+
stage1Survivors.push(entry);
|
|
220
|
+
} else {
|
|
221
|
+
stage1Rejected.push(entry.candidate.word);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
208
224
|
|
|
209
|
-
|
|
225
|
+
// Grammar Filter
|
|
226
|
+
} catch (err) {
|
|
227
|
+
_iterator3.e(err);
|
|
228
|
+
} finally {
|
|
229
|
+
_iterator3.f();
|
|
230
|
+
}
|
|
210
231
|
var _applyGrammarFilter = applyGrammarFilter(stage1Survivors, previousWord),
|
|
211
232
|
filtered = _applyGrammarFilter.filtered,
|
|
212
233
|
grammarMeta = _applyGrammarFilter.grammarMeta;
|
|
213
234
|
|
|
214
235
|
// Stage 2 + final assembly
|
|
215
236
|
var lmMax = 0;
|
|
216
|
-
if (lmLogits
|
|
237
|
+
if (lmLogits) {
|
|
217
238
|
var values = Object.values(lmLogits);
|
|
218
|
-
lmMax = Math.max.apply(Math, values);
|
|
239
|
+
if (values.length > 0) lmMax = Math.max.apply(Math, values);
|
|
219
240
|
}
|
|
220
241
|
var scored = filtered.map(function (entry) {
|
|
221
242
|
var lmScore = 0;
|
|
222
243
|
var finalScore = entry.stage1Score;
|
|
223
|
-
if (lmLogits &&
|
|
244
|
+
if (lmLogits && lmMax >= LM_GATE_THRESHOLD) {
|
|
224
245
|
var rawLm = getLmScore(entry.candidate.word, lmLogits);
|
|
225
246
|
if (rawLm !== 0) {
|
|
226
|
-
// The word was in the top_k! Score it normally.
|
|
227
247
|
var logitDiff = Math.log(rawLm) - Math.log(lmMax);
|
|
228
248
|
lmScore = Math.exp(logitDiff);
|
|
229
|
-
} else {
|
|
230
|
-
lmScore = 0.05;
|
|
231
249
|
}
|
|
250
|
+
// Words absent from the prefix-filtered payload get lmScore = 0,
|
|
251
|
+
// not 0.05, so they don't outrank genuine LM predictions.
|
|
252
|
+
|
|
232
253
|
finalScore = STAGE1_WEIGHT * entry.stage1Score + STAGE2_WEIGHT * lmScore;
|
|
233
254
|
}
|
|
234
255
|
return {
|
|
@@ -240,13 +261,17 @@ export function rankCandidates(candidates, contextVector, getWordVector, lmLogit
|
|
|
240
261
|
};
|
|
241
262
|
});
|
|
242
263
|
scored.sort(function (a, b) {
|
|
243
|
-
if (b.finalScore !== a.finalScore)
|
|
244
|
-
return b.finalScore - a.finalScore;
|
|
245
|
-
}
|
|
264
|
+
if (b.finalScore !== a.finalScore) return b.finalScore - a.finalScore;
|
|
246
265
|
return a.word.length - b.word.length;
|
|
247
266
|
});
|
|
248
267
|
return {
|
|
249
268
|
candidates: scored,
|
|
250
|
-
grammarMeta: grammarMeta
|
|
269
|
+
grammarMeta: grammarMeta,
|
|
270
|
+
pipelineDebug: {
|
|
271
|
+
initial: candidates.length,
|
|
272
|
+
stage1Rejected: stage1Rejected,
|
|
273
|
+
grammarRejected: (_grammarMeta$dropped = grammarMeta === null || grammarMeta === void 0 ? void 0 : grammarMeta.dropped) !== null && _grammarMeta$dropped !== void 0 ? _grammarMeta$dropped : [],
|
|
274
|
+
final: scored.length
|
|
275
|
+
}
|
|
251
276
|
};
|
|
252
277
|
}
|
|
@@ -29,7 +29,7 @@ import l3VocabularyData from './data/l3_vocabulary.json';
|
|
|
29
29
|
import vocabularyData from './data/vocabulary_10k.json';
|
|
30
30
|
import wordIndexData from './data/word_index_10k.json';
|
|
31
31
|
// import { rankCandidates, isGrammarAllowed } from './scoring-pipeline';
|
|
32
|
-
import { rankCandidates } from './scoring-pipeline';
|
|
32
|
+
import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
|
|
33
33
|
import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
|
|
34
34
|
|
|
35
35
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -570,11 +570,21 @@ export var predict = function predict(textBefore) {
|
|
|
570
570
|
sessionFreq: node.sessionFreq
|
|
571
571
|
};
|
|
572
572
|
});
|
|
573
|
+
|
|
574
|
+
// Filter the LM payload to only words matching the current prefix so that
|
|
575
|
+
// lmMax in rankCandidates reflects prefix-relevant signal, not the global distribution.
|
|
576
|
+
var prefix = currentWord.toLowerCase();
|
|
577
|
+
var prefixLmLogits = lmLogits ? Object.fromEntries(Object.entries(lmLogits).filter(function (_ref4) {
|
|
578
|
+
var _ref5 = _slicedToArray(_ref4, 1),
|
|
579
|
+
word = _ref5[0];
|
|
580
|
+
return word.startsWith(prefix);
|
|
581
|
+
})) : null;
|
|
573
582
|
var _rankCandidates = rankCandidates(scoringCandidates, contextVector, function (w) {
|
|
574
583
|
return getWordVector(w);
|
|
575
|
-
},
|
|
584
|
+
}, prefixLmLogits, wordTrie.maxTenantFreq, previousWord),
|
|
576
585
|
ranked = _rankCandidates.candidates,
|
|
577
|
-
grammarMeta = _rankCandidates.grammarMeta
|
|
586
|
+
grammarMeta = _rankCandidates.grammarMeta,
|
|
587
|
+
pipelineDebug = _rankCandidates.pipelineDebug;
|
|
578
588
|
var best = ranked[0];
|
|
579
589
|
var suggestion = best && best.finalScore >= MIN_SCORE_THRESHOLD ? best.word.slice(currentWord.length) : null;
|
|
580
590
|
if (debugMode) {
|
|
@@ -621,7 +631,7 @@ export var predict = function predict(textBefore) {
|
|
|
621
631
|
console.log('%cSlow Lane:', 'color: #888; font-style: italic;', vectorStatus, '|', logitsStatus);
|
|
622
632
|
|
|
623
633
|
// 4. Scoring formula active this prediction
|
|
624
|
-
var formulaLabel = lmLogits && Object.keys(lmLogits).length > 0 ?
|
|
634
|
+
var formulaLabel = lmLogits && Object.keys(lmLogits).length > 0 ? "Stage1(\xD7".concat(STAGE1_WEIGHT, ") + LM(\xD7").concat(STAGE2_WEIGHT, ")") : 'Stage1 only (no LM logits)';
|
|
625
635
|
// eslint-disable-next-line no-console
|
|
626
636
|
console.log('%cFormula:', 'color: #888; font-style: italic;', formulaLabel);
|
|
627
637
|
|
|
@@ -635,17 +645,21 @@ export var predict = function predict(textBefore) {
|
|
|
635
645
|
}
|
|
636
646
|
}
|
|
637
647
|
|
|
638
|
-
// 6.
|
|
648
|
+
// 6. Pipeline funnel
|
|
649
|
+
// eslint-disable-next-line no-console
|
|
650
|
+
console.log("%c[Pipeline Funnel] %c\uD83D\uDCE5 In: ".concat(pipelineDebug.initial, " | \u274C Stage 1 (< ").concat(MIN_STAGE1_SCORE, "): -").concat(pipelineDebug.stage1Rejected.length, " | \u274C Grammar: -").concat(pipelineDebug.grammarRejected.length, " | \u2705 Final: ").concat(pipelineDebug.final), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
|
|
651
|
+
|
|
652
|
+
// 7. Candidate table
|
|
639
653
|
if (ranked.length > 0) {
|
|
640
654
|
var lmCoverage = ranked.slice(0, 10).filter(function (r) {
|
|
641
|
-
return r.lmScore > 0
|
|
655
|
+
return r.lmScore > 0;
|
|
642
656
|
}).length;
|
|
643
657
|
// eslint-disable-next-line no-console
|
|
644
658
|
console.log("%cLM coverage: ".concat(lmCoverage, "/").concat(Math.min(ranked.length, 10), " candidates had real logit scores"), 'color: #888; font-style: italic;');
|
|
645
659
|
var tableData = ranked.slice(0, 10).map(function (r) {
|
|
646
660
|
var rawLogit = 'Not in Payload';
|
|
647
|
-
if (
|
|
648
|
-
var val =
|
|
661
|
+
if (prefixLmLogits) {
|
|
662
|
+
var val = prefixLmLogits[r.word.toLowerCase()];
|
|
649
663
|
if (val !== undefined) {
|
|
650
664
|
rawLogit = Number(val.toFixed(5));
|
|
651
665
|
}
|
|
@@ -691,7 +705,7 @@ export var predict = function predict(textBefore) {
|
|
|
691
705
|
// ─── Data Loading ────────────────────────────────────────────────────────────
|
|
692
706
|
|
|
693
707
|
export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
694
|
-
var
|
|
708
|
+
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
|
|
695
709
|
var url, res, buffer, float32, wordIndex, nWords, dim;
|
|
696
710
|
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
697
711
|
while (1) switch (_context.prev = _context.next) {
|
|
@@ -777,7 +791,7 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
777
791
|
}, _callee, null, [[6, 12], [17, 37]]);
|
|
778
792
|
}));
|
|
779
793
|
return function loadVectorsAsync(_x) {
|
|
780
|
-
return
|
|
794
|
+
return _ref6.apply(this, arguments);
|
|
781
795
|
};
|
|
782
796
|
}();
|
|
783
797
|
export var initVectors = function initVectors(store) {
|
|
@@ -786,10 +800,10 @@ export var initVectors = function initVectors(store) {
|
|
|
786
800
|
export var loadDefaultVocabulary = function loadDefaultVocabulary() {
|
|
787
801
|
// 1. Load the Atlassian Domain (L2)
|
|
788
802
|
var data = vocabularyData;
|
|
789
|
-
var terms = Object.entries(data.words).map(function (
|
|
790
|
-
var
|
|
791
|
-
word =
|
|
792
|
-
stats =
|
|
803
|
+
var terms = Object.entries(data.words).map(function (_ref7) {
|
|
804
|
+
var _ref8 = _slicedToArray(_ref7, 2),
|
|
805
|
+
word = _ref8[0],
|
|
806
|
+
stats = _ref8[1];
|
|
793
807
|
return {
|
|
794
808
|
word: word,
|
|
795
809
|
freq: stats.freq,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { NextEditorPlugin } from '@atlaskit/editor-common/types';
|
|
1
|
+
import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
|
|
2
|
+
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
|
|
2
3
|
import type { AutocompletePluginOptions, AutocompletePluginState } from './pm-plugins/autocomplete-plugin';
|
|
3
4
|
export type AutocompletePlugin = NextEditorPlugin<'autocomplete', {
|
|
4
5
|
pluginConfiguration?: AutocompletePluginOptions | undefined;
|
|
5
6
|
sharedState: AutocompletePluginState | undefined;
|
|
7
|
+
dependencies: [OptionalPlugin<AnalyticsPlugin>];
|
|
6
8
|
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
2
|
+
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
2
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
3
4
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
import type { AutocompletePlugin } from '../autocompletePluginType';
|
|
4
6
|
export declare const autocompletePluginKey: PluginKey<any>;
|
|
5
7
|
export interface AutocompletePluginState {
|
|
6
8
|
/** The decoration set containing the ghost text widget */
|
|
@@ -39,4 +41,4 @@ export interface AutocompletePluginOptions {
|
|
|
39
41
|
*/
|
|
40
42
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
41
43
|
}
|
|
42
|
-
export declare const createAutocompletePlugin: (options?: AutocompletePluginOptions) => SafePlugin<AutocompletePluginState>;
|
|
44
|
+
export declare const createAutocompletePlugin: (options?: AutocompletePluginOptions, api?: ExtractInjectionAPI<AutocompletePlugin>) => SafePlugin<AutocompletePluginState>;
|
|
@@ -26,8 +26,18 @@ export interface GrammarFilterMeta {
|
|
|
26
26
|
prevTags: string[];
|
|
27
27
|
prevWord: string;
|
|
28
28
|
}
|
|
29
|
+
export declare const STAGE1_WEIGHT = 0.35;
|
|
30
|
+
export declare const STAGE2_WEIGHT = 0.65;
|
|
31
|
+
export declare const MIN_STAGE1_SCORE = 0.35;
|
|
32
|
+
export interface PipelineDebug {
|
|
33
|
+
final: number;
|
|
34
|
+
grammarRejected: string[];
|
|
35
|
+
initial: number;
|
|
36
|
+
stage1Rejected: string[];
|
|
37
|
+
}
|
|
29
38
|
export interface RankCandidatesResult {
|
|
30
39
|
candidates: ScoredCandidate[];
|
|
31
40
|
grammarMeta: GrammarFilterMeta | null;
|
|
41
|
+
pipelineDebug: PipelineDebug;
|
|
32
42
|
}
|
|
33
43
|
export declare function rankCandidates(candidates: ScoringCandidate[], contextVector: Float32Array | null, getWordVector: (word: string) => Float32Array | null, lmLogits: Record<string, number> | null, maxTenantFreq: number, previousWord: string): RankCandidatesResult;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { NextEditorPlugin } from '@atlaskit/editor-common/types';
|
|
1
|
+
import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
|
|
2
|
+
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
|
|
2
3
|
import type { AutocompletePluginOptions, AutocompletePluginState } from './pm-plugins/autocomplete-plugin';
|
|
3
4
|
export type AutocompletePlugin = NextEditorPlugin<'autocomplete', {
|
|
4
5
|
pluginConfiguration?: AutocompletePluginOptions | undefined;
|
|
5
6
|
sharedState: AutocompletePluginState | undefined;
|
|
7
|
+
dependencies: [
|
|
8
|
+
OptionalPlugin<AnalyticsPlugin>
|
|
9
|
+
];
|
|
6
10
|
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
2
|
+
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
2
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
3
4
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
5
|
+
import type { AutocompletePlugin } from '../autocompletePluginType';
|
|
4
6
|
export declare const autocompletePluginKey: PluginKey<any>;
|
|
5
7
|
export interface AutocompletePluginState {
|
|
6
8
|
/** The decoration set containing the ghost text widget */
|
|
@@ -39,4 +41,4 @@ export interface AutocompletePluginOptions {
|
|
|
39
41
|
*/
|
|
40
42
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
41
43
|
}
|
|
42
|
-
export declare const createAutocompletePlugin: (options?: AutocompletePluginOptions) => SafePlugin<AutocompletePluginState>;
|
|
44
|
+
export declare const createAutocompletePlugin: (options?: AutocompletePluginOptions, api?: ExtractInjectionAPI<AutocompletePlugin>) => SafePlugin<AutocompletePluginState>;
|
|
@@ -26,8 +26,18 @@ export interface GrammarFilterMeta {
|
|
|
26
26
|
prevTags: string[];
|
|
27
27
|
prevWord: string;
|
|
28
28
|
}
|
|
29
|
+
export declare const STAGE1_WEIGHT = 0.35;
|
|
30
|
+
export declare const STAGE2_WEIGHT = 0.65;
|
|
31
|
+
export declare const MIN_STAGE1_SCORE = 0.35;
|
|
32
|
+
export interface PipelineDebug {
|
|
33
|
+
final: number;
|
|
34
|
+
grammarRejected: string[];
|
|
35
|
+
initial: number;
|
|
36
|
+
stage1Rejected: string[];
|
|
37
|
+
}
|
|
29
38
|
export interface RankCandidatesResult {
|
|
30
39
|
candidates: ScoredCandidate[];
|
|
31
40
|
grammarMeta: GrammarFilterMeta | null;
|
|
41
|
+
pipelineDebug: PipelineDebug;
|
|
32
42
|
}
|
|
33
43
|
export declare function rankCandidates(candidates: ScoringCandidate[], contextVector: Float32Array | null, getWordVector: (word: string) => Float32Array | null, lmLogits: Record<string, number> | null, maxTenantFreq: number, previousWord: string): RankCandidatesResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"wink-nlp": "^2.4.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@atlaskit/editor-common": "^114.
|
|
36
|
+
"@atlaskit/editor-common": "^114.5.0",
|
|
37
|
+
"@atlaskit/editor-plugin-analytics": "^10.0.0",
|
|
37
38
|
"react": "^18.2.0"
|
|
38
39
|
},
|
|
39
40
|
"techstack": {
|
|
@@ -2,7 +2,7 @@ import type { AutocompletePlugin } from './autocompletePluginType';
|
|
|
2
2
|
import { autocompletePluginKey, createAutocompletePlugin } from './pm-plugins/autocomplete-plugin';
|
|
3
3
|
import type { AutocompletePluginState } from './pm-plugins/autocomplete-plugin';
|
|
4
4
|
|
|
5
|
-
export const autocompletePlugin: AutocompletePlugin = ({ config: options }) => {
|
|
5
|
+
export const autocompletePlugin: AutocompletePlugin = ({ config: options, api }) => {
|
|
6
6
|
return {
|
|
7
7
|
name: 'autocomplete',
|
|
8
8
|
getSharedState(editorState): AutocompletePluginState | undefined {
|
|
@@ -16,7 +16,7 @@ export const autocompletePlugin: AutocompletePlugin = ({ config: options }) => {
|
|
|
16
16
|
return [
|
|
17
17
|
{
|
|
18
18
|
name: 'autocomplete',
|
|
19
|
-
plugin: () => createAutocompletePlugin(options),
|
|
19
|
+
plugin: () => createAutocompletePlugin(options, api),
|
|
20
20
|
},
|
|
21
21
|
];
|
|
22
22
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { NextEditorPlugin } from '@atlaskit/editor-common/types';
|
|
1
|
+
import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/types';
|
|
2
|
+
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
|
|
2
3
|
|
|
3
4
|
import type {
|
|
4
5
|
AutocompletePluginOptions,
|
|
@@ -10,5 +11,6 @@ export type AutocompletePlugin = NextEditorPlugin<
|
|
|
10
11
|
{
|
|
11
12
|
pluginConfiguration?: AutocompletePluginOptions | undefined;
|
|
12
13
|
sharedState: AutocompletePluginState | undefined;
|
|
14
|
+
dependencies: [OptionalPlugin<AnalyticsPlugin>];
|
|
13
15
|
}
|
|
14
16
|
>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
|
|
1
2
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
|
+
import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
|
|
2
4
|
import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
|
|
3
5
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
6
|
import type {
|
|
@@ -9,6 +11,8 @@ import type {
|
|
|
9
11
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
10
12
|
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
11
13
|
|
|
14
|
+
import type { AutocompletePlugin } from '../autocompletePluginType';
|
|
15
|
+
|
|
12
16
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
13
17
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
14
18
|
import {
|
|
@@ -90,6 +94,8 @@ const setAutocompleteMeta = (
|
|
|
90
94
|
/**
|
|
91
95
|
* Apply a ghost text suggestion to the editor state.
|
|
92
96
|
*/
|
|
97
|
+
let lastShownGhostText = '';
|
|
98
|
+
|
|
93
99
|
const showGhostText = (view: EditorView, text: string, position: number): void => {
|
|
94
100
|
const { state, dispatch } = view;
|
|
95
101
|
const decorationSet = createGhostTextDecorationSet(state, position, text);
|
|
@@ -196,7 +202,7 @@ const buildSlowLaneText = (docText: string, context?: AutocompleteContext): stri
|
|
|
196
202
|
return lines.join('\n');
|
|
197
203
|
};
|
|
198
204
|
|
|
199
|
-
export const createAutocompletePlugin = (options?: AutocompletePluginOptions) => {
|
|
205
|
+
export const createAutocompletePlugin = (options?: AutocompletePluginOptions, api?: ExtractInjectionAPI<AutocompletePlugin>) => {
|
|
200
206
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
201
207
|
let hasIngestedPage = false;
|
|
202
208
|
let resolvedContext: AutocompleteContext | undefined;
|
|
@@ -258,6 +264,14 @@ export const createAutocompletePlugin = (options?: AutocompletePluginOptions) =>
|
|
|
258
264
|
|
|
259
265
|
if (prediction && prediction.length > 0) {
|
|
260
266
|
showGhostText(view, prediction, selection.from);
|
|
267
|
+
if (prediction !== lastShownGhostText) {
|
|
268
|
+
lastShownGhostText = prediction;
|
|
269
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
270
|
+
action: ACTION.SUGGESTION_VIEWED,
|
|
271
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
272
|
+
eventType: EVENT_TYPE.TRACK,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
261
275
|
}
|
|
262
276
|
}, DEBOUNCE_MS);
|
|
263
277
|
};
|
|
@@ -346,12 +360,28 @@ export const createAutocompletePlugin = (options?: AutocompletePluginOptions) =>
|
|
|
346
360
|
handleKeyDown: keydownHandler({
|
|
347
361
|
Tab: (state: EditorState, dispatch?: (tr: Transaction) => void) => {
|
|
348
362
|
const accepted = acceptGhostText(state, dispatch);
|
|
349
|
-
if (accepted)
|
|
363
|
+
if (accepted) {
|
|
364
|
+
justAccepted = true;
|
|
365
|
+
lastShownGhostText = '';
|
|
366
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
367
|
+
action: ACTION.SUGGESTION_INSERTED,
|
|
368
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
369
|
+
eventType: EVENT_TYPE.TRACK,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
350
372
|
return accepted;
|
|
351
373
|
},
|
|
352
374
|
ArrowRight: (state: EditorState, dispatch?: (tr: Transaction) => void) => {
|
|
353
375
|
const accepted = acceptGhostText(state, dispatch);
|
|
354
|
-
if (accepted)
|
|
376
|
+
if (accepted) {
|
|
377
|
+
justAccepted = true;
|
|
378
|
+
lastShownGhostText = '';
|
|
379
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
380
|
+
action: ACTION.SUGGESTION_INSERTED,
|
|
381
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
382
|
+
eventType: EVENT_TYPE.TRACK,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
355
385
|
return accepted;
|
|
356
386
|
},
|
|
357
387
|
Escape: (state: EditorState, dispatch?: (tr: Transaction) => void) => {
|
|
@@ -49,10 +49,15 @@ interface GrammarTransitions {
|
|
|
49
49
|
const ALPHA = 0.5;
|
|
50
50
|
const BETA = 0.5;
|
|
51
51
|
const NEUTRAL_SCORE = 0.5;
|
|
52
|
-
const STAGE1_WEIGHT = 0.
|
|
53
|
-
const STAGE2_WEIGHT = 0.
|
|
54
|
-
const MIN_STAGE1_SCORE = 0.35;
|
|
52
|
+
export const STAGE1_WEIGHT = 0.35;
|
|
53
|
+
export const STAGE2_WEIGHT = 0.65;
|
|
54
|
+
export const MIN_STAGE1_SCORE = 0.35;
|
|
55
55
|
const L1_SESSION_CAP = 1.2;
|
|
56
|
+
// Minimum prefix-payload max LM probability before Stage 2 activates.
|
|
57
|
+
// Below this threshold the LM signal is too weak to suppress Stage 1 — finalScore
|
|
58
|
+
// falls back to stage1Score directly. Prevents weak prefixes (e.g. "ins" → "instances"
|
|
59
|
+
// at 0.00024) from triggering re-ranking.
|
|
60
|
+
const LM_GATE_THRESHOLD = 0.0005;
|
|
56
61
|
|
|
57
62
|
// ─── Grammar Data (loaded once on import) ───────────────────
|
|
58
63
|
|
|
@@ -217,9 +222,17 @@ function getLmScore(word: string, lmLogits: Record<string, number> | null): numb
|
|
|
217
222
|
|
|
218
223
|
// ─── Public API ─────────────────────────────────────────────
|
|
219
224
|
|
|
225
|
+
export interface PipelineDebug {
|
|
226
|
+
final: number;
|
|
227
|
+
grammarRejected: string[];
|
|
228
|
+
initial: number;
|
|
229
|
+
stage1Rejected: string[];
|
|
230
|
+
}
|
|
231
|
+
|
|
220
232
|
export interface RankCandidatesResult {
|
|
221
233
|
candidates: ScoredCandidate[];
|
|
222
234
|
grammarMeta: GrammarFilterMeta | null;
|
|
235
|
+
pipelineDebug: PipelineDebug;
|
|
223
236
|
}
|
|
224
237
|
|
|
225
238
|
export function rankCandidates(
|
|
@@ -240,33 +253,39 @@ export function rankCandidates(
|
|
|
240
253
|
);
|
|
241
254
|
return { candidate, semanticScore, freqScore, stage1Score };
|
|
242
255
|
});
|
|
243
|
-
|
|
244
|
-
const
|
|
256
|
+
const stage1Survivors: typeof stage1Results = [];
|
|
257
|
+
const stage1Rejected: string[] = [];
|
|
258
|
+
for (const entry of stage1Results) {
|
|
259
|
+
if (entry.stage1Score >= MIN_STAGE1_SCORE) {
|
|
260
|
+
stage1Survivors.push(entry);
|
|
261
|
+
} else {
|
|
262
|
+
stage1Rejected.push(entry.candidate.word);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
245
265
|
|
|
246
266
|
// Grammar Filter
|
|
247
267
|
const { filtered, grammarMeta } = applyGrammarFilter(stage1Survivors, previousWord);
|
|
248
268
|
|
|
249
269
|
// Stage 2 + final assembly
|
|
250
270
|
let lmMax = 0;
|
|
251
|
-
if (lmLogits
|
|
271
|
+
if (lmLogits) {
|
|
252
272
|
const values = Object.values(lmLogits);
|
|
253
|
-
lmMax = Math.max(...values);
|
|
273
|
+
if (values.length > 0) lmMax = Math.max(...values);
|
|
254
274
|
}
|
|
255
275
|
|
|
256
276
|
const scored: ScoredCandidate[] = filtered.map((entry) => {
|
|
257
277
|
let lmScore = 0;
|
|
258
278
|
let finalScore = entry.stage1Score;
|
|
259
279
|
|
|
260
|
-
if (lmLogits &&
|
|
280
|
+
if (lmLogits && lmMax >= LM_GATE_THRESHOLD) {
|
|
261
281
|
const rawLm = getLmScore(entry.candidate.word, lmLogits);
|
|
262
282
|
|
|
263
283
|
if (rawLm !== 0) {
|
|
264
|
-
// The word was in the top_k! Score it normally.
|
|
265
284
|
const logitDiff = Math.log(rawLm) - Math.log(lmMax);
|
|
266
285
|
lmScore = Math.exp(logitDiff);
|
|
267
|
-
} else {
|
|
268
|
-
lmScore = 0.05;
|
|
269
286
|
}
|
|
287
|
+
// Words absent from the prefix-filtered payload get lmScore = 0,
|
|
288
|
+
// not 0.05, so they don't outrank genuine LM predictions.
|
|
270
289
|
|
|
271
290
|
finalScore = STAGE1_WEIGHT * entry.stage1Score + STAGE2_WEIGHT * lmScore;
|
|
272
291
|
}
|
|
@@ -281,11 +300,18 @@ export function rankCandidates(
|
|
|
281
300
|
});
|
|
282
301
|
|
|
283
302
|
scored.sort((a, b) => {
|
|
284
|
-
if (b.finalScore !== a.finalScore)
|
|
285
|
-
return b.finalScore - a.finalScore;
|
|
286
|
-
}
|
|
303
|
+
if (b.finalScore !== a.finalScore) return b.finalScore - a.finalScore;
|
|
287
304
|
return a.word.length - b.word.length;
|
|
288
305
|
});
|
|
289
306
|
|
|
290
|
-
return {
|
|
307
|
+
return {
|
|
308
|
+
candidates: scored,
|
|
309
|
+
grammarMeta,
|
|
310
|
+
pipelineDebug: {
|
|
311
|
+
initial: candidates.length,
|
|
312
|
+
stage1Rejected,
|
|
313
|
+
grammarRejected: grammarMeta?.dropped ?? [],
|
|
314
|
+
final: scored.length,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
291
317
|
}
|