@atlaskit/editor-plugin-autocomplete 2.5.2 → 3.1.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.
@@ -18,11 +18,9 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
18
18
 
19
19
  import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
20
20
 
21
- // import bigramsData from './data/bigrams.json';
22
- import l3VocabularyData from './data/l3_vocabulary.json';
23
- import vocabularyData from './data/vocabulary_10k.json';
24
- import wordIndexData from './data/word_index_10k.json';
25
- // import { rankCandidates, isGrammarAllowed } from './scoring-pipeline';
21
+ // The vocabulary, L3 and word-index JSON payloads are dynamically imported in
22
+ // loadDefaultVocabulary / loadVectorsAsync so their (large) contents stay out of
23
+ // the editor's main chunk and only load when autocomplete is initialised.
26
24
  import { isAutocompleteDebugEnabled } from './debug-mode';
27
25
  import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
28
26
  import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
@@ -34,7 +32,7 @@ const PUNCTUATION_BOUNDARY_REGEX = /^[.,;:!?()\[\]{}"'`]+|[.,;:!?()\[\]{}"'`]+$/
34
32
  const MIN_PREFIX_LENGTH = 3;
35
33
  const MAX_CANDIDATES = 200;
36
34
  const CONTEXT_WORDS = 10;
37
- const MIN_SCORE_THRESHOLD = 0.2;
35
+ const MIN_SCORE_THRESHOLD = 0.35;
38
36
  const L3_BASELINE_FREQ = 0.001;
39
37
 
40
38
  // ─── Types ───────────────────────────────────────────────────────────────────
@@ -153,7 +151,6 @@ const wordTrie = new WeightedWordTrie();
153
151
  // L3 Trie (General English Fallback)
154
152
  const l3Trie = new WeightedWordTrie();
155
153
 
156
- // --- Initialization Function ---
157
154
  /**
158
155
  * Loads the General English vocabulary.
159
156
  * expects a simple array of strings: ["about", "above", "actually", ...]
@@ -249,14 +246,11 @@ const tokenize = text => {
249
246
  return tokens;
250
247
  };
251
248
  const extractPreviousWord = text => {
252
- // 1. Split the text by newlines or punctuation (. ? !)
249
+ // Only consider the current sentence/line the user is typing in.
253
250
  // eslint-disable-next-line require-unicode-regexp
254
251
  const sentences = text.split(/[\n.?!]+/);
255
-
256
- // 2. Only look at the current sentence/line the user is typing in
257
252
  const currentSentence = sentences[sentences.length - 1];
258
253
 
259
- // 3. Extract the previous word as normal
260
254
  // eslint-disable-next-line require-unicode-regexp
261
255
  const words = currentSentence.trimEnd().split(/\s+/);
262
256
  return words.length >= 2 ? words[words.length - 2] : '';
@@ -310,7 +304,6 @@ export const incrementSessionFreq = word => {
310
304
  * Pass `undefined` (or omit the argument) to skip priming — useful when the
311
305
  * calling context does not yet have a page value available.
312
306
  */
313
- // NOTE: We ingest full page context here
314
307
  export const ingestDocumentPage = pageContent => {
315
308
  if (!pageContent) {
316
309
  return;
@@ -334,7 +327,11 @@ export const ingestDocumentPage = pageContent => {
334
327
  };
335
328
  export const predict = textBefore => {
336
329
  if (!isInitialized) {
337
- loadDefaultVocabulary();
330
+ // Vocabulary JSON is code-split and loads asynchronously. Kick off the load
331
+ // and skip this keystroke; the plugin also primes it on focus, so the tries
332
+ // are usually ready before the user types.
333
+ void loadDefaultVocabulary().catch(() => {});
334
+ return null;
338
335
  }
339
336
  const t0 = performance.now();
340
337
 
@@ -386,19 +383,15 @@ export const predict = textBefore => {
386
383
  if (currentWord.length < MIN_PREFIX_LENGTH) {
387
384
  return null;
388
385
  }
389
-
390
- // 1. Primary Query: Ask the L2 Domain Trie
391
386
  const candidates = wordTrie.getCandidates(currentWord, MAX_CANDIDATES);
392
387
 
393
- // 2. Fallback Query: Gap-fill with the L3 General English Trie
388
+ // Gap-fill from the L3 general-English trie, requesting a full buffer so
389
+ // enough survive de-duplication against the L2 results.
394
390
  if (candidates.length < MAX_CANDIDATES) {
395
- // Ask L3 for MAX_CANDIDATES to guarantee we have enough buffer
396
- // to survive the deduplication process.
397
391
  const l3Candidates = l3Trie.getCandidates(currentWord, MAX_CANDIDATES);
398
392
  const existingWords = new Set(candidates.map(c => c.word));
399
393
  for (const l3c of l3Candidates) {
400
- if (candidates.length >= MAX_CANDIDATES) break; // Stop exactly at the limit
401
-
394
+ if (candidates.length >= MAX_CANDIDATES) break;
402
395
  if (!existingWords.has(l3c.word)) {
403
396
  candidates.push(l3c);
404
397
  }
@@ -558,6 +551,41 @@ export const predict = textBefore => {
558
551
 
559
552
  // ─── Data Loading ────────────────────────────────────────────────────────────
560
553
 
554
+ /**
555
+ * Unwrap a dynamically imported JSON module to its parsed value, handling both
556
+ * interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
557
+ * (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
558
+ * modules, where `default` can be a misleading scalar). Named exports are
559
+ * preferred when present. The caller declares the JSON `shape` because a dense
560
+ * array and a sparse numeric-keyed object are emitted identically as named
561
+ * exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
562
+ */
563
+ const unwrapJsonModule = (mod, shape) => {
564
+ if (mod == null || typeof mod !== 'object') {
565
+ return null;
566
+ }
567
+ const namespace = mod;
568
+ const ownKeys = Object.keys(namespace).filter(k => k !== 'default' && k !== '__esModule');
569
+ if (ownKeys.length > 0) {
570
+ if (shape === 'array') {
571
+ const len = ownKeys.length;
572
+ const arr = new Array(len);
573
+ for (let i = 0; i < len; i++) {
574
+ arr[i] = namespace[String(i)];
575
+ }
576
+ return arr;
577
+ }
578
+ const obj = {};
579
+ for (const k of ownKeys) {
580
+ obj[k] = namespace[k];
581
+ }
582
+ return obj;
583
+ }
584
+ if ('default' in namespace && namespace.default != null) {
585
+ return namespace.default;
586
+ }
587
+ return null;
588
+ };
561
589
  export const loadVectorsAsync = async options => {
562
590
  if (vectorStore || vectorsLoadStarted) {
563
591
  return;
@@ -582,6 +610,7 @@ export const loadVectorsAsync = async options => {
582
610
  return;
583
611
  }
584
612
  try {
613
+ var _wordIndexOuter$index;
585
614
  const res = await fetch(url);
586
615
  if (!res.ok) {
587
616
  vectorsLoadStarted = false;
@@ -595,8 +624,18 @@ export const loadVectorsAsync = async options => {
595
624
  }
596
625
  const buffer = await res.arrayBuffer();
597
626
  const float32 = new Float32Array(buffer);
598
- const wordIndex = wordIndexData;
627
+
628
+ // word_index_10k.json is wrapped as `{ "index": {…} }` so no real entry
629
+ // (e.g. the word "default") can shadow the synthetic ESM `default` export
630
+ // the bundler creates for dynamically-imported JSON.
631
+ const wordIndexModule = await import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-word-index-10k" */'./data/word_index_10k.json');
632
+ const wordIndexOuter = unwrapJsonModule(wordIndexModule, 'object');
633
+ const wordIndex = (_wordIndexOuter$index = wordIndexOuter === null || wordIndexOuter === void 0 ? void 0 : wordIndexOuter.index) !== null && _wordIndexOuter$index !== void 0 ? _wordIndexOuter$index : {};
599
634
  const nWords = Object.keys(wordIndex).length;
635
+ if (nWords === 0) {
636
+ // eslint-disable-next-line no-console
637
+ console.warn('[text-predictor] word_index_10k.json missing its `index` wrapper — wordIndex is empty, semantic scoring will be a no-op.');
638
+ }
600
639
  const dim = float32.length / nWords;
601
640
  vectorStore = {
602
641
  float32,
@@ -628,35 +667,51 @@ export const loadVectorsAsync = async options => {
628
667
  export const initVectors = store => {
629
668
  vectorStore = store;
630
669
  };
670
+ let vocabularyLoadPromise;
631
671
  export const loadDefaultVocabulary = () => {
632
672
  if (isInitialized) {
633
- return;
634
- }
635
- startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
636
- try {
637
- // 1. Load the Atlassian Domain (L2)
638
- const data = vocabularyData;
639
- const terms = Object.entries(data.words).map(([word, stats]) => ({
640
- word,
641
- freq: stats.freq,
642
- docFreq: stats.doc_freq,
643
- authorFreq: stats.author_freq
644
- }));
645
- initVocabulary({
646
- terms
647
- });
648
-
649
- // 2. Load General English (L3)
650
- const l3Words = l3VocabularyData;
651
- initL3Vocabulary(l3Words);
652
- succeedExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
653
- l2WordCount: terms.length,
654
- l3WordCount: l3Words.length
655
- });
656
- } catch (e) {
657
- failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
658
- errorType: 'parse_error'
659
- });
660
- throw e;
661
- }
673
+ return Promise.resolve();
674
+ }
675
+ if (vocabularyLoadPromise) {
676
+ return vocabularyLoadPromise;
677
+ }
678
+ vocabularyLoadPromise = (async () => {
679
+ startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
680
+ try {
681
+ // The L2 vocabulary and L3 word list are code-split into their own async
682
+ // chunks so they stay out of the editor's main bundle.
683
+ const [vocabularyModule, l3VocabularyModule] = await Promise.all([import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */'./data/vocabulary_10k.json'), import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-l3-vocabulary" */'./data/l3_vocabulary.json')]);
684
+ const vocabularyData = unwrapJsonModule(vocabularyModule, 'object');
685
+ const l3VocabularyData = unwrapJsonModule(l3VocabularyModule, 'array');
686
+ if ((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null || !Array.isArray(l3VocabularyData)) {
687
+ throw new Error('[text-predictor] vocabulary JSON modules could not be unwrapped');
688
+ }
689
+ const terms = Object.entries(vocabularyData.words).map(([word, stats]) => ({
690
+ word,
691
+ freq: stats.freq,
692
+ docFreq: stats.doc_freq,
693
+ authorFreq: stats.author_freq
694
+ }));
695
+
696
+ // Load L3 before L2: initVocabulary flips `isInitialized = true`, so it
697
+ // must run last — otherwise a throw in initL3Vocabulary would strand
698
+ // `isInitialized` true and the retry path could never reload L3.
699
+ initL3Vocabulary(l3VocabularyData);
700
+ initVocabulary({
701
+ terms
702
+ });
703
+ succeedExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
704
+ l2WordCount: terms.length,
705
+ l3WordCount: l3VocabularyData.length
706
+ });
707
+ } catch (e) {
708
+ failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
709
+ errorType: 'parse_error'
710
+ });
711
+ // Allow a later call to retry the load rather than caching the failure.
712
+ vocabularyLoadPromise = undefined;
713
+ throw e;
714
+ }
715
+ })();
716
+ return vocabularyLoadPromise;
662
717
  };
@@ -31,8 +31,6 @@ var createInitialState = function createInitialState() {
31
31
  var getTextBeforeCursor = function getTextBeforeCursor(state) {
32
32
  var $from = state.selection.$from;
33
33
  var maxChars = 200;
34
-
35
- // 1. Get the perfectly flattened text of the current block up to the cursor
36
34
  var blockNode = $from.parent;
37
35
  var offsetInBlock = $from.parentOffset;
38
36
  var blockText = blockNode.textContent.slice(0, offsetInBlock);
@@ -41,7 +39,7 @@ var getTextBeforeCursor = function getTextBeforeCursor(state) {
41
39
  }
42
40
  var fullText = blockText;
43
41
 
44
- // 2. Walk backwards through previous blocks
42
+ // Walk backwards through previous blocks until we have enough context.
45
43
  var depth = $from.depth - 1;
46
44
  while (fullText.length < maxChars && depth >= 0) {
47
45
  var parentNode = $from.node(depth);
@@ -242,8 +240,6 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
242
240
  try {
243
241
  var state = view.state;
244
242
  var selection = state.selection;
245
-
246
- // Only predict for cursor selections (not range selections)
247
243
  if (!selection.empty) {
248
244
  return;
249
245
  }
@@ -255,13 +251,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
255
251
  return;
256
252
  }
257
253
  dismissedContext = null;
258
-
259
- // Don't predict if there's not enough context
260
254
  if (textBefore.trim().length < 3) {
261
255
  return;
262
256
  }
263
-
264
- // Tier 1 prediction is synchronous -- no async needed
265
257
  var prediction = predict(textBefore);
266
258
  if (prediction && prediction.length > 0) {
267
259
  var typedLength = getTypedLengthForPrediction(textBefore);
@@ -323,8 +315,7 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
323
315
  return _objectSpread(_objectSpread({}, pluginState), meta);
324
316
  }
325
317
 
326
- // If the document changed, clear the ghost text
327
- // (new prediction will be scheduled from view.update)
318
+ // A new prediction is scheduled from view.update.
328
319
  if (tr.docChanged) {
329
320
  return _objectSpread(_objectSpread({}, pluginState), {}, {
330
321
  ghostText: '',
@@ -332,8 +323,6 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
332
323
  decorationSet: DecorationSet.empty
333
324
  });
334
325
  }
335
-
336
- // If selection changed without doc change, clear ghost text
337
326
  if (tr.selectionSet && pluginState.ghostText) {
338
327
  return _objectSpread(_objectSpread({}, pluginState), {}, {
339
328
  ghostText: '',
@@ -394,13 +383,11 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
394
383
  return false;
395
384
  },
396
385
  focus: function focus() {
397
- try {
398
- loadDefaultVocabulary();
399
- } catch (error) {
386
+ loadDefaultVocabulary().catch(function (error) {
400
387
  logException(error, {
401
388
  location: 'editor-plugin-autocomplete/loadDefaultVocabulary'
402
389
  });
403
- }
390
+ });
404
391
  loadVectorsAsync({
405
392
  getBinaryUrl: options === null || options === void 0 ? void 0 : options.getVectorsBinaryUrl
406
393
  }).catch(function (error) {
@@ -442,12 +429,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
442
429
  if (justAccepted) {
443
430
  justAccepted = false;
444
431
 
445
- // ✨ THE FIX: Memorize the text state right after acceptance.
446
- // Any follow-up transactions will hit the 'dismissedContext'
447
- // block and abort until the user actually types a new character!
432
+ // Snapshot the post-acceptance text so follow-up transactions hit
433
+ // the dismissedContext guard and abort until the user types again.
448
434
  dismissedContext = getTextBeforeCursor(view.state);
449
-
450
- // Also clear any pending debounce timers from before the acceptance
451
435
  if (debounceTimer) {
452
436
  clearTimeout(debounceTimer);
453
437
  }
@@ -121,8 +121,8 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
121
121
  };
122
122
  var initEngine = /*#__PURE__*/function () {
123
123
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
124
- var _yield$import, CreateMLCEngine, prebuiltAppConfig, customModelRecord, appConfig, errorMsg;
125
- return _regeneratorRuntime.wrap(function _callee$(_context) {
124
+ var _yield$import, CreateMLCEngine, prebuiltAppConfig, customModelRecord, appConfig, errorMsg, _t;
125
+ return _regeneratorRuntime.wrap(function (_context) {
126
126
  while (1) switch (_context.prev = _context.next) {
127
127
  case 0:
128
128
  _context.prev = 0;
@@ -132,14 +132,14 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
132
132
  }
133
133
  onStatus === null || onStatus === void 0 || onStatus("Initialising model: ".concat(modelId, "\u2026"));
134
134
  if ('gpu' in navigator) {
135
- _context.next = 5;
135
+ _context.next = 1;
136
136
  break;
137
137
  }
138
138
  throw new Error('WebGPU not supported');
139
- case 5:
140
- _context.next = 7;
139
+ case 1:
140
+ _context.next = 2;
141
141
  return import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-mlc-web-llm" */'@mlc-ai/web-llm');
142
- case 7:
142
+ case 2:
143
143
  _yield$import = _context.sent;
144
144
  CreateMLCEngine = _yield$import.CreateMLCEngine;
145
145
  prebuiltAppConfig = _yield$import.prebuiltAppConfig;
@@ -159,34 +159,34 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
159
159
  appConfig = {
160
160
  model_list: [].concat(_toConsumableArray(prebuiltAppConfig.model_list), _toConsumableArray(customModelRecord ? [customModelRecord] : []))
161
161
  };
162
- _context.next = 14;
162
+ _context.next = 3;
163
163
  return CreateMLCEngine(modelId, {
164
164
  appConfig: appConfig,
165
165
  initProgressCallback: initProgressCallback
166
166
  });
167
- case 14:
167
+ case 3:
168
168
  engine = _context.sent;
169
169
  if (!destroyed) {
170
- _context.next = 19;
170
+ _context.next = 4;
171
171
  break;
172
172
  }
173
173
  // destroy() was called while we were loading — clean up
174
174
  unloadEngine(engine);
175
175
  engine = null;
176
176
  return _context.abrupt("return");
177
- case 19:
177
+ case 4:
178
178
  ready = true;
179
179
  if (isAutocompleteDebugEnabled()) {
180
180
  // eslint-disable-next-line no-console
181
181
  console.log('%c[LocalSlowLane] %c✅ MLC engine loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
182
182
  }
183
183
  onStatus === null || onStatus === void 0 || onStatus('Model loaded and ready.');
184
- _context.next = 32;
184
+ _context.next = 6;
185
185
  break;
186
- case 24:
187
- _context.prev = 24;
188
- _context.t0 = _context["catch"](0);
189
- errorMsg = _context.t0 instanceof Error ? _context.t0.message : String(_context.t0);
186
+ case 5:
187
+ _context.prev = 5;
188
+ _t = _context["catch"](0);
189
+ errorMsg = _t instanceof Error ? _t.message : String(_t);
190
190
  ready = false;
191
191
  if (isAutocompleteDebugEnabled()) {
192
192
  // eslint-disable-next-line no-console
@@ -195,11 +195,11 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
195
195
  onStatus === null || onStatus === void 0 || onStatus("Engine initialisation failed: ".concat(errorMsg));
196
196
  engineInitPromise = null;
197
197
  initFailed = true;
198
- case 32:
198
+ case 6:
199
199
  case "end":
200
200
  return _context.stop();
201
201
  }
202
- }, _callee, null, [[0, 24]]);
202
+ }, _callee, null, [[0, 5]]);
203
203
  }));
204
204
  return function initEngine() {
205
205
  return _ref.apply(this, arguments);
@@ -226,18 +226,18 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
226
226
  */
227
227
  var runInference = /*#__PURE__*/function () {
228
228
  var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(text, requestId) {
229
- var _response$choices, response, lmLogits, logprobsContent, tokenLogprobs, token, _iterator, _step, alt, _token, logitValues, topTokens, errorMsg;
230
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
229
+ var _response$choices, response, lmLogits, logprobsContent, tokenLogprobs, token, _iterator, _step, alt, _token, logitValues, topTokens, errorMsg, _t2;
230
+ return _regeneratorRuntime.wrap(function (_context2) {
231
231
  while (1) switch (_context2.prev = _context2.next) {
232
232
  case 0:
233
233
  if (!(!engine || destroyed)) {
234
- _context2.next = 2;
234
+ _context2.next = 1;
235
235
  break;
236
236
  }
237
237
  return _context2.abrupt("return");
238
- case 2:
239
- _context2.prev = 2;
240
- _context2.next = 5;
238
+ case 1:
239
+ _context2.prev = 1;
240
+ _context2.next = 2;
241
241
  return engine.chat.completions.create({
242
242
  messages: [{
243
243
  role: 'user',
@@ -248,14 +248,14 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
248
248
  top_logprobs: 5,
249
249
  temperature: 0
250
250
  });
251
- case 5:
251
+ case 2:
252
252
  response = _context2.sent;
253
253
  if (!(requestId < latestRequestId || destroyed)) {
254
- _context2.next = 8;
254
+ _context2.next = 3;
255
255
  break;
256
256
  }
257
257
  return _context2.abrupt("return");
258
- case 8:
258
+ case 3:
259
259
  // ── Extract LM logits ───────────────────────────────────────
260
260
  lmLogits = {};
261
261
  logprobsContent = (_response$choices = response.choices) === null || _response$choices === void 0 || (_response$choices = _response$choices[0]) === null || _response$choices === void 0 || (_response$choices = _response$choices.logprobs) === null || _response$choices === void 0 ? void 0 : _response$choices.content;
@@ -333,17 +333,17 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
333
333
  hasVector: storedContextVector !== null,
334
334
  hasLmLogits: storedLmLogits !== null
335
335
  });
336
- _context2.next = 26;
336
+ _context2.next = 6;
337
337
  break;
338
- case 17:
339
- _context2.prev = 17;
340
- _context2.t0 = _context2["catch"](2);
338
+ case 4:
339
+ _context2.prev = 4;
340
+ _t2 = _context2["catch"](1);
341
341
  if (!(requestId < latestRequestId)) {
342
- _context2.next = 21;
342
+ _context2.next = 5;
343
343
  break;
344
344
  }
345
345
  return _context2.abrupt("return");
346
- case 21:
346
+ case 5:
347
347
  storedContextVector = null;
348
348
  storedLmLogits = null;
349
349
  onUpdate === null || onUpdate === void 0 || onUpdate({
@@ -351,16 +351,16 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
351
351
  hasVector: false,
352
352
  hasLmLogits: false
353
353
  });
354
- errorMsg = _context2.t0 instanceof Error ? _context2.t0.message : String(_context2.t0);
354
+ errorMsg = _t2 instanceof Error ? _t2.message : String(_t2);
355
355
  if (isAutocompleteDebugEnabled()) {
356
356
  // eslint-disable-next-line no-console
357
357
  console.log("%c[LocalSlowLane] %c\u274C Inference error (request #".concat(requestId, "): ").concat(errorMsg), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
358
358
  }
359
- case 26:
359
+ case 6:
360
360
  case "end":
361
361
  return _context2.stop();
362
362
  }
363
- }, _callee2, null, [[2, 17]]);
363
+ }, _callee2, null, [[1, 4]]);
364
364
  }));
365
365
  return function runInference(_x, _x2) {
366
366
  return _ref2.apply(this, arguments);
@@ -161,20 +161,21 @@ function applyGrammarFilter(candidates, previousWord) {
161
161
  dropped.push(entry.candidate.word);
162
162
  }
163
163
  }
164
+
165
+ // Grammar is authoritative.
164
166
  } catch (err) {
165
167
  _iterator2.e(err);
166
168
  } finally {
167
169
  _iterator2.f();
168
170
  }
169
- var finalFiltered = filtered.length > 0 ? filtered : candidates;
170
171
  return {
171
- filtered: finalFiltered,
172
+ filtered: filtered,
172
173
  grammarMeta: {
173
174
  prevWord: lowerPrev,
174
175
  prevTags: prevTags,
175
176
  before: candidates.length,
176
- after: finalFiltered.length,
177
- dropped: filtered.length > 0 ? dropped : []
177
+ after: filtered.length,
178
+ dropped: dropped
178
179
  }
179
180
  };
180
181
  }
@@ -64,16 +64,16 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
64
64
  var inflightRequestId = null;
65
65
  var doUpdateContext = /*#__PURE__*/function () {
66
66
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(text, requestId) {
67
- var url, payload, res, data;
68
- return _regeneratorRuntime.wrap(function _callee$(_context) {
67
+ var url, payload, res, data, _t;
68
+ return _regeneratorRuntime.wrap(function (_context) {
69
69
  while (1) switch (_context.prev = _context.next) {
70
70
  case 0:
71
71
  if (!(!text || text.trim().length === 0)) {
72
- _context.next = 2;
72
+ _context.next = 1;
73
73
  break;
74
74
  }
75
75
  return _context.abrupt("return");
76
- case 2:
76
+ case 1:
77
77
  // eslint-disable-next-line require-unicode-regexp
78
78
  url = "".concat(baseUrl.replace(/\/$/, '')).concat(endpoint);
79
79
  payload = {
@@ -93,17 +93,17 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
93
93
  // eslint-disable-next-line no-console
94
94
  console.groupEnd();
95
95
  }
96
- _context.prev = 6;
97
- _context.next = 9;
96
+ _context.prev = 2;
97
+ _context.next = 3;
98
98
  return fetchFn(url, {
99
99
  method: 'POST',
100
100
  headers: headers,
101
101
  body: JSON.stringify(payload)
102
102
  });
103
- case 9:
103
+ case 3:
104
104
  res = _context.sent;
105
105
  if (res.ok) {
106
- _context.next = 16;
106
+ _context.next = 4;
107
107
  break;
108
108
  }
109
109
  storedContextVector = null;
@@ -117,10 +117,10 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
117
117
  console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
118
118
  }
119
119
  return _context.abrupt("return");
120
- case 16:
121
- _context.next = 18;
120
+ case 4:
121
+ _context.next = 5;
122
122
  return res.json();
123
- case 18:
123
+ case 5:
124
124
  data = _context.sent;
125
125
  if (data.semantic_vector && Array.isArray(data.semantic_vector)) {
126
126
  storedContextVector = new Float32Array(data.semantic_vector);
@@ -153,11 +153,11 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
153
153
  hasLmLogits: storedLmLogits !== null
154
154
  });
155
155
  // eslint-disable-next-line no-unused-vars
156
- _context.next = 32;
156
+ _context.next = 7;
157
157
  break;
158
- case 26:
159
- _context.prev = 26;
160
- _context.t0 = _context["catch"](6);
158
+ case 6:
159
+ _context.prev = 6;
160
+ _t = _context["catch"](2);
161
161
  storedContextVector = null;
162
162
  storedLmLogits = null;
163
163
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
@@ -167,17 +167,17 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
167
167
  // eslint-disable-next-line no-console
168
168
  console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
169
169
  }
170
- case 32:
171
- _context.prev = 32;
170
+ case 7:
171
+ _context.prev = 7;
172
172
  if (inflightRequestId === requestId) {
173
173
  inflightRequestId = null;
174
174
  }
175
- return _context.finish(32);
176
- case 35:
175
+ return _context.finish(7);
176
+ case 8:
177
177
  case "end":
178
178
  return _context.stop();
179
179
  }
180
- }, _callee, null, [[6, 26, 32, 35]]);
180
+ }, _callee, null, [[2, 6, 7, 8]]);
181
181
  }));
182
182
  return function doUpdateContext(_x, _x2) {
183
183
  return _ref.apply(this, arguments);