@atlaskit/editor-plugin-autocomplete 0.1.0 → 0.3.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/afm-cc/tsconfig.json +2 -1
  3. package/afm-jira/tsconfig.json +2 -1
  4. package/afm-products/tsconfig.json +2 -1
  5. package/build/tsconfig.json +20 -0
  6. package/build/url-module.d.ts +8 -0
  7. package/dist/cjs/autocompletePlugin.js +18 -5
  8. package/dist/cjs/autocompletePluginType.js +5 -1
  9. package/dist/cjs/pm-plugins/autocomplete-plugin.js +368 -0
  10. package/dist/cjs/pm-plugins/ghost-text-decoration.js +39 -0
  11. package/dist/cjs/pm-plugins/scoring-pipeline.js +256 -0
  12. package/dist/cjs/pm-plugins/slow-lane-client.js +199 -0
  13. package/dist/cjs/pm-plugins/text-predictor.js +796 -0
  14. package/dist/es2019/autocompletePlugin.js +20 -6
  15. package/dist/es2019/autocompletePluginType.js +1 -0
  16. package/dist/es2019/pm-plugins/autocomplete-plugin.js +368 -0
  17. package/dist/es2019/pm-plugins/ghost-text-decoration.js +33 -0
  18. package/dist/es2019/pm-plugins/scoring-pipeline.js +221 -0
  19. package/dist/es2019/pm-plugins/slow-lane-client.js +157 -0
  20. package/dist/es2019/pm-plugins/text-predictor.js +631 -0
  21. package/dist/esm/autocompletePlugin.js +18 -5
  22. package/dist/esm/autocompletePluginType.js +1 -0
  23. package/dist/esm/pm-plugins/autocomplete-plugin.js +362 -0
  24. package/dist/esm/pm-plugins/ghost-text-decoration.js +33 -0
  25. package/dist/esm/pm-plugins/scoring-pipeline.js +252 -0
  26. package/dist/esm/pm-plugins/slow-lane-client.js +192 -0
  27. package/dist/esm/pm-plugins/text-predictor.js +793 -0
  28. package/dist/types/autocompletePluginType.d.ts +6 -3
  29. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +36 -0
  30. package/dist/types/pm-plugins/ghost-text-decoration.d.ts +7 -0
  31. package/dist/types/pm-plugins/scoring-pipeline.d.ts +33 -0
  32. package/dist/types/pm-plugins/slow-lane-client.d.ts +46 -0
  33. package/dist/types/pm-plugins/text-predictor.d.ts +90 -0
  34. package/dist/types-ts4.5/autocompletePluginType.d.ts +6 -3
  35. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +36 -0
  36. package/dist/types-ts4.5/pm-plugins/ghost-text-decoration.d.ts +7 -0
  37. package/dist/types-ts4.5/pm-plugins/scoring-pipeline.d.ts +33 -0
  38. package/dist/types-ts4.5/pm-plugins/slow-lane-client.d.ts +46 -0
  39. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +90 -0
  40. package/package.json +2 -2
  41. package/src/autocompletePlugin.tsx +25 -5
  42. package/src/autocompletePluginType.ts +14 -3
  43. package/src/pm-plugins/autocomplete-plugin/package.json +15 -0
  44. package/src/pm-plugins/autocomplete-plugin.ts +443 -0
  45. package/src/pm-plugins/data/combined_l2_l3_pos_tags.json +73571 -3
  46. package/src/pm-plugins/data/ghost_pos_tags.json +43 -3
  47. package/src/pm-plugins/data/grammar_transitions_10k.json +46 -3
  48. package/src/pm-plugins/data/l3_vocabulary.json +20002 -3
  49. package/src/pm-plugins/data/vocabulary_10k.json +38794 -3
  50. package/src/pm-plugins/data/word_index_10k.json +7760 -3
  51. package/src/pm-plugins/ghost-text-decoration.ts +44 -0
  52. package/src/pm-plugins/scoring-pipeline.ts +294 -0
  53. package/src/pm-plugins/slow-lane-client/package.json +15 -0
  54. package/src/pm-plugins/slow-lane-client.ts +222 -0
  55. package/src/pm-plugins/text-predictor/package.json +15 -0
  56. package/src/pm-plugins/text-predictor.ts +780 -0
  57. package/tsconfig.app.json +12 -3
  58. package/tsconfig.json +4 -1
@@ -0,0 +1,796 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.setDebugMode = exports.predict = exports.loadVectorsAsync = exports.loadDefaultVocabulary = exports.initVocabulary = exports.initVectors = exports.initL3Vocabulary = exports.ingestDocumentPage = exports.incrementSessionFreq = exports.getPredictorStatus = exports.getLastPredictionDebug = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
12
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
13
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
14
+ var _l3_vocabulary = _interopRequireDefault(require("./data/l3_vocabulary.json"));
15
+ var _vocabulary_10k = _interopRequireDefault(require("./data/vocabulary_10k.json"));
16
+ var _word_index_10k = _interopRequireDefault(require("./data/word_index_10k.json"));
17
+ var _scoringPipeline = require("./scoring-pipeline");
18
+ var _slowLaneClient = require("./slow-lane-client");
19
+ 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; } } }; }
20
+ 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; } }
21
+ 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; } /**
22
+ * Fast Lane Predictor: Local autocomplete using weighted trie + frequency + semantic scoring.
23
+ *
24
+ * Two prediction modes:
25
+ * 1. Word boundary → bigram-based next-word suggestion (grammar-filtered)
26
+ * 2. Mid-word (≥3 chars) → trie prefix search → scoring pipeline → top result
27
+ *
28
+ * Scoring is delegated to scoring-pipeline.ts which handles:
29
+ * Stage 1 (semantic + frequency), grammar filter, Stage 2 (optional LM re-ranking).
30
+ *
31
+ * Context vector: average of word vectors from text before cursor (last N words).
32
+ * Falls back to cold mode (freq-only) when vectors not yet loaded.
33
+ *
34
+ * Session personalization (L1): words the user types are incrementally boosted
35
+ * via incrementSessionFreq(), called on word boundaries from the plugin.
36
+ */ // import bigramsData from './data/bigrams.json';
37
+ // import { rankCandidates, isGrammarAllowed } from './scoring-pipeline';
38
+ // ─── Constants ───────────────────────────────────────────────────────────────
39
+
40
+ // eslint-disable-next-line require-unicode-regexp
41
+ var PUNCTUATION_BOUNDARY_REGEX = /^[.,;:!?()\[\]{}"'`]+|[.,;:!?()\[\]{}"'`]+$/g;
42
+ var MIN_PREFIX_LENGTH = 3;
43
+ var MAX_CANDIDATES = 200;
44
+ var CONTEXT_WORDS = 10;
45
+ var MIN_SCORE_THRESHOLD = 0.2;
46
+ var L3_BASELINE_FREQ = 0.001;
47
+
48
+ // ─── Types ───────────────────────────────────────────────────────────────────
49
+ var TrieNode = /*#__PURE__*/(0, _createClass2.default)(function TrieNode() {
50
+ (0, _classCallCheck2.default)(this, TrieNode);
51
+ (0, _defineProperty2.default)(this, "children", new Map());
52
+ (0, _defineProperty2.default)(this, "word", null);
53
+ (0, _defineProperty2.default)(this, "tenantFreq", 0);
54
+ (0, _defineProperty2.default)(this, "docFreq", 0);
55
+ (0, _defineProperty2.default)(this, "authorFreq", 0);
56
+ (0, _defineProperty2.default)(this, "sessionFreq", 0);
57
+ });
58
+ var WeightedWordTrie = /*#__PURE__*/function () {
59
+ function WeightedWordTrie() {
60
+ (0, _classCallCheck2.default)(this, WeightedWordTrie);
61
+ (0, _defineProperty2.default)(this, "root", new TrieNode());
62
+ /** Highest tenantFreq seen — used to normalize freq scores at query time */
63
+ (0, _defineProperty2.default)(this, "maxTenantFreq", 1);
64
+ }
65
+ return (0, _createClass2.default)(WeightedWordTrie, [{
66
+ key: "insert",
67
+ value: function insert(word, tenantFreq, docFreq, authorFreq) {
68
+ var node = this.root;
69
+ var _iterator = _createForOfIteratorHelper(word.toLowerCase()),
70
+ _step;
71
+ try {
72
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
73
+ var char = _step.value;
74
+ var next = node.children.get(char);
75
+ if (!next) {
76
+ next = new TrieNode();
77
+ node.children.set(char, next);
78
+ }
79
+ node = next;
80
+ }
81
+ } catch (err) {
82
+ _iterator.e(err);
83
+ } finally {
84
+ _iterator.f();
85
+ }
86
+ node.word = word;
87
+ node.tenantFreq = tenantFreq;
88
+ node.docFreq = docFreq;
89
+ node.authorFreq = authorFreq;
90
+ if (tenantFreq > this.maxTenantFreq) {
91
+ this.maxTenantFreq = tenantFreq;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Return all words matching this prefix, up to maxResults.
97
+ * O(prefix_length + results) — traverses to the prefix node then collects subtree.
98
+ */
99
+ }, {
100
+ key: "getCandidates",
101
+ value: function getCandidates(prefix) {
102
+ var maxResults = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : MAX_CANDIDATES;
103
+ var node = this.root;
104
+ var _iterator2 = _createForOfIteratorHelper(prefix.toLowerCase()),
105
+ _step2;
106
+ try {
107
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
108
+ var char = _step2.value;
109
+ var next = node.children.get(char);
110
+ if (!next) {
111
+ return [];
112
+ }
113
+ node = next;
114
+ }
115
+ } catch (err) {
116
+ _iterator2.e(err);
117
+ } finally {
118
+ _iterator2.f();
119
+ }
120
+ var candidates = [];
121
+ var stack = [node];
122
+ while (stack.length > 0 && candidates.length < maxResults) {
123
+ var current = stack.pop();
124
+ if (!current) {
125
+ continue;
126
+ }
127
+
128
+ // Only add candidates that are longer than the prefix
129
+ if (current.word && current.word.length > prefix.length) {
130
+ candidates.push({
131
+ word: current.word,
132
+ node: current
133
+ });
134
+ }
135
+ var _iterator3 = _createForOfIteratorHelper(current.children.values()),
136
+ _step3;
137
+ try {
138
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
139
+ var child = _step3.value;
140
+ stack.push(child);
141
+ }
142
+ } catch (err) {
143
+ _iterator3.e(err);
144
+ } finally {
145
+ _iterator3.f();
146
+ }
147
+ }
148
+ return candidates;
149
+ }
150
+ }, {
151
+ key: "findNode",
152
+ value: function findNode(word) {
153
+ var node = this.root;
154
+ var _iterator4 = _createForOfIteratorHelper(word.toLowerCase()),
155
+ _step4;
156
+ try {
157
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
158
+ var char = _step4.value;
159
+ var next = node.children.get(char);
160
+ if (!next) {
161
+ return null;
162
+ }
163
+ node = next;
164
+ }
165
+ } catch (err) {
166
+ _iterator4.e(err);
167
+ } finally {
168
+ _iterator4.f();
169
+ }
170
+ return node.word !== null ? node : null;
171
+ }
172
+
173
+ /**
174
+ * Set the session frequency for a word.
175
+ * Returns true if the word exists in the trie.
176
+ */
177
+ }, {
178
+ key: "updateSessionFreq",
179
+ value: function updateSessionFreq(word, count) {
180
+ var node = this.findNode(word);
181
+ if (!node) {
182
+ return false;
183
+ }
184
+ node.sessionFreq = count;
185
+ return true;
186
+ }
187
+
188
+ /**
189
+ * Increment the session frequency for a word by 1.
190
+ * Returns true if the word exists in the trie.
191
+ */
192
+ }, {
193
+ key: "incrementSessionFreq",
194
+ value: function incrementSessionFreq(word) {
195
+ var node = this.findNode(word);
196
+ if (!node) {
197
+ return false;
198
+ }
199
+ node.sessionFreq += 1;
200
+ return true;
201
+ }
202
+ }]);
203
+ }(); // L1/L2 Trie (Session + Atlassian Domain)
204
+ var wordTrie = new WeightedWordTrie();
205
+
206
+ // L3 Trie (General English Fallback)
207
+ var l3Trie = new WeightedWordTrie();
208
+
209
+ // --- Initialization Function ---
210
+ /**
211
+ * Loads the General English vocabulary.
212
+ * expects a simple array of strings: ["about", "above", "actually", ...]
213
+ */
214
+ var initL3Vocabulary = exports.initL3Vocabulary = function initL3Vocabulary(l3Words) {
215
+ var _iterator5 = _createForOfIteratorHelper(l3Words),
216
+ _step5;
217
+ try {
218
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
219
+ var word = _step5.value;
220
+ // Insert with a tiny baseline frequency so it mathematically
221
+ // loses to any domain word in Stage 1, but still scores above 0.
222
+ l3Trie.insert(word, L3_BASELINE_FREQ, 0, 0);
223
+ }
224
+ } catch (err) {
225
+ _iterator5.e(err);
226
+ } finally {
227
+ _iterator5.f();
228
+ }
229
+ if (debugMode) {
230
+ // eslint-disable-next-line no-console
231
+ console.log("[text-predictor] L3 General English loaded: ".concat(l3Words.length, " words"));
232
+ }
233
+ };
234
+
235
+ // const bigramMap: Map<string, Record<string, number>> = new Map(
236
+ // Object.entries(bigramsData as Record<string, Record<string, number>>),
237
+ // );
238
+
239
+ var isInitialized = false;
240
+ var vectorStore = null;
241
+ var vectorsLoadStarted = false;
242
+ var debugMode = true;
243
+ var lastPredictionDebug = null;
244
+ var hasLoggedSemanticActive = false;
245
+
246
+ /** Get vector for a word from the store. */
247
+ var getWordVector = function getWordVector(word) {
248
+ if (!vectorStore) {
249
+ return null;
250
+ }
251
+ var idx = vectorStore.wordIndex[word.toLowerCase()];
252
+ if (idx === undefined) {
253
+ return null;
254
+ }
255
+ var start = idx * vectorStore.dim;
256
+ return vectorStore.float32.subarray(start, start + vectorStore.dim);
257
+ };
258
+
259
+ /**
260
+ * Compute context vector by averaging vectors of last N words in text.
261
+ * Falls back to null (cold mode) if no words have vectors.
262
+ */
263
+ var computeContextVectorLocal = function computeContextVectorLocal(textBefore) {
264
+ if (!vectorStore) {
265
+ return null;
266
+ }
267
+ var tokens = tokenize(textBefore);
268
+ var words = tokens.slice(-CONTEXT_WORDS);
269
+ var vectors = [];
270
+ var _iterator6 = _createForOfIteratorHelper(words),
271
+ _step6;
272
+ try {
273
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
274
+ var word = _step6.value;
275
+ var _v = getWordVector(word);
276
+ if (_v) {
277
+ vectors.push(_v);
278
+ }
279
+ }
280
+ } catch (err) {
281
+ _iterator6.e(err);
282
+ } finally {
283
+ _iterator6.f();
284
+ }
285
+ if (vectors.length === 0) {
286
+ return null;
287
+ }
288
+ var dim = vectorStore.dim;
289
+ var avg = new Float32Array(dim);
290
+ for (var _i = 0, _vectors = vectors; _i < _vectors.length; _i++) {
291
+ var v = _vectors[_i];
292
+ for (var i = 0; i < dim; i++) {
293
+ avg[i] += v[i];
294
+ }
295
+ }
296
+ for (var _i2 = 0; _i2 < dim; _i2++) {
297
+ avg[_i2] /= vectors.length;
298
+ }
299
+ return avg;
300
+ };
301
+
302
+ /**
303
+ * Get context vector for scoring. Prefers Slow Lane (BE) context when available
304
+ * and dimension matches; otherwise falls back to local averaging.
305
+ */
306
+ var getContextVectorForScoring = function getContextVectorForScoring(textBefore) {
307
+ var slowLaneVector = (0, _slowLaneClient.getStoredContextVector)();
308
+ if (slowLaneVector && vectorStore && slowLaneVector.length === vectorStore.dim) {
309
+ return slowLaneVector;
310
+ }
311
+ return computeContextVectorLocal(textBefore);
312
+ };
313
+ var tokenize = function tokenize(text) {
314
+ var tokens = [];
315
+ // eslint-disable-next-line require-unicode-regexp, @atlassian/perf-linting/no-expensive-split-replace
316
+ var _iterator7 = _createForOfIteratorHelper(text.toLowerCase().split(/\s+/)),
317
+ _step7;
318
+ try {
319
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
320
+ var raw = _step7.value;
321
+ // eslint-disable-next-line @atlassian/perf-linting/no-expensive-split-replace
322
+ var clean = raw.replace(PUNCTUATION_BOUNDARY_REGEX, '');
323
+ if (clean.length >= 2) {
324
+ tokens.push(clean);
325
+ }
326
+ }
327
+ } catch (err) {
328
+ _iterator7.e(err);
329
+ } finally {
330
+ _iterator7.f();
331
+ }
332
+ return tokens;
333
+ };
334
+ var extractPreviousWord = function extractPreviousWord(text) {
335
+ // 1. Split the text by newlines or punctuation (. ? !)
336
+ // eslint-disable-next-line require-unicode-regexp
337
+ var sentences = text.split(/[\n.?!]+/);
338
+
339
+ // 2. Only look at the current sentence/line the user is typing in
340
+ var currentSentence = sentences[sentences.length - 1];
341
+
342
+ // 3. Extract the previous word as normal
343
+ // eslint-disable-next-line require-unicode-regexp
344
+ var words = currentSentence.trimEnd().split(/\s+/);
345
+ return words.length >= 2 ? words[words.length - 2] : '';
346
+ };
347
+
348
+ // ─── Debug Helpers ───────────────────────────────────────────────────────────
349
+
350
+ /** Enable or disable debug logging. Also checks localStorage key `autocomplete-debug`. */
351
+ var setDebugMode = exports.setDebugMode = function setDebugMode(enabled) {
352
+ debugMode = enabled;
353
+ };
354
+
355
+ /** Check localStorage for autocomplete-debug on first access. */
356
+ var ensureDebugModeFromStorage = function ensureDebugModeFromStorage() {
357
+ if (typeof localStorage !== 'undefined' && localStorage.getItem('autocomplete-debug') === '1') {
358
+ debugMode = true;
359
+ }
360
+ };
361
+
362
+ /**
363
+ * Get predictor status for debugging.
364
+ * vectorsLoaded: true when semantic scoring is active
365
+ * wordCount: number of words in vector store (0 if not loaded)
366
+ */
367
+ var getPredictorStatus = exports.getPredictorStatus = function getPredictorStatus() {
368
+ ensureDebugModeFromStorage();
369
+ return {
370
+ vectorsLoaded: vectorStore !== null,
371
+ wordCount: vectorStore ? Object.keys(vectorStore.wordIndex).length : 0,
372
+ vectorsLoadStarted: vectorsLoadStarted,
373
+ isInitialized: isInitialized
374
+ };
375
+ };
376
+
377
+ /**
378
+ * Get details of the last prediction (for debugging).
379
+ * Returns null if no prediction has run yet or debug was off.
380
+ */
381
+ var getLastPredictionDebug = exports.getLastPredictionDebug = function getLastPredictionDebug() {
382
+ ensureDebugModeFromStorage();
383
+ return lastPredictionDebug;
384
+ };
385
+ var initVocabulary = exports.initVocabulary = function initVocabulary(vocabulary) {
386
+ var _iterator8 = _createForOfIteratorHelper(vocabulary.terms),
387
+ _step8;
388
+ try {
389
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
390
+ var term = _step8.value;
391
+ wordTrie.insert(term.word, term.freq, term.docFreq, term.authorFreq);
392
+ }
393
+ } catch (err) {
394
+ _iterator8.e(err);
395
+ } finally {
396
+ _iterator8.f();
397
+ }
398
+ isInitialized = true;
399
+ };
400
+
401
+ /**
402
+ * Increment L1 session frequency for a single word.
403
+ * Called from the plugin on word boundaries for efficient incremental boosting.
404
+ */
405
+ var incrementSessionFreq = exports.incrementSessionFreq = function incrementSessionFreq(word) {
406
+ wordTrie.incrementSessionFreq(word);
407
+ };
408
+
409
+ /**
410
+ * Prime session frequencies from a document page string.
411
+ *
412
+ * Iterates through every token in `pageContent` and increments its session
413
+ * frequency so that words already present on the page receive an L1 boost
414
+ * before the user starts typing.
415
+ *
416
+ * Pass `undefined` (or omit the argument) to skip priming — useful when the
417
+ * calling context does not yet have a page value available.
418
+ */
419
+ // NOTE: We ingest full page context here
420
+ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPage(pageContent) {
421
+ if (!pageContent) {
422
+ return;
423
+ }
424
+ var words = tokenize(pageContent);
425
+ var validBoostedWords = new Set();
426
+ var _iterator9 = _createForOfIteratorHelper(words),
427
+ _step9;
428
+ try {
429
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
430
+ var word = _step9.value;
431
+ var didBoost = wordTrie.incrementSessionFreq(word);
432
+ if (didBoost) {
433
+ validBoostedWords.add(word);
434
+ }
435
+ }
436
+ } catch (err) {
437
+ _iterator9.e(err);
438
+ } finally {
439
+ _iterator9.f();
440
+ }
441
+ if (debugMode && validBoostedWords.size > 0) {
442
+ // eslint-disable-next-line no-console
443
+ console.groupCollapsed("%c[L1 Session] %cPrimed ".concat(validBoostedWords.size, " valid dictionary words from page"), 'color: #00b8d9; font-weight: bold;', 'color: inherit; font-style: italic;');
444
+ // eslint-disable-next-line no-console
445
+ console.dir(Array.from(validBoostedWords).sort());
446
+ // eslint-disable-next-line no-console
447
+ console.groupEnd();
448
+ }
449
+ };
450
+ var predict = exports.predict = function predict(textBefore) {
451
+ ensureDebugModeFromStorage();
452
+ if (!isInitialized) {
453
+ loadDefaultVocabulary();
454
+ }
455
+ var t0 = performance.now();
456
+
457
+ // ── Step 1: Bigram-based next-word suggestion at word boundary ───────────
458
+ // if (textBefore.length > 0 && /\s$/u.test(textBefore)) {
459
+ // const words = textBefore.toLowerCase().trimEnd().split(/\s+/u);
460
+ // const prevWord = words[words.length - 1];
461
+ // const nextWords = bigramMap.get(prevWord);
462
+ // if (nextWords) {
463
+ // const sorted = Object.entries(nextWords).sort((a, b) => b[1] - a[1]);
464
+ // let bestWord = '';
465
+ // for (const [word] of sorted) {
466
+ // if (!isGrammarAllowed(prevWord, word)) {
467
+ // continue;
468
+ // }
469
+ // bestWord = word;
470
+ // break;
471
+ // }
472
+ // if (bestWord) {
473
+ // if (debugMode) {
474
+ // const latencyMs = performance.now() - t0;
475
+ // console.log(
476
+ // '%c[autocomplete] BIGRAM',
477
+ // 'color:cyan',
478
+ // '| "' + prevWord + '" -> "' + bestWord + '" | ' + latencyMs.toFixed(1) + 'ms',
479
+ // );
480
+ // }
481
+ // return bestWord;
482
+ // }
483
+ // }
484
+ // if (debugMode) {
485
+ // console.log(
486
+ // '%c[autocomplete] BIGRAM-MISS',
487
+ // 'color:gray',
488
+ // '| no bigram for "' + words[words.length - 1] + '", skipping prefix completion',
489
+ // );
490
+ // }
491
+ // return null;
492
+ // }
493
+
494
+ // ── Step 2: Prefix completion (≥3 chars typed) ──────────────────────────
495
+ // eslint-disable-next-line require-unicode-regexp
496
+ if (textBefore.length > 0 && /\s$/.test(textBefore)) {
497
+ return null;
498
+ }
499
+ var trimmed = textBefore.trimEnd();
500
+ var lastSpaceIdx = trimmed.lastIndexOf(' ');
501
+ var currentWord = lastSpaceIdx === -1 ? trimmed : trimmed.slice(lastSpaceIdx + 1);
502
+ if (currentWord.length < MIN_PREFIX_LENGTH) {
503
+ return null;
504
+ }
505
+
506
+ // 1. Primary Query: Ask the L2 Domain Trie
507
+ var candidates = wordTrie.getCandidates(currentWord, MAX_CANDIDATES);
508
+
509
+ // 2. Fallback Query: Gap-fill with the L3 General English Trie
510
+ if (candidates.length < MAX_CANDIDATES) {
511
+ // Ask L3 for MAX_CANDIDATES to guarantee we have enough buffer
512
+ // to survive the deduplication process.
513
+ var l3Candidates = l3Trie.getCandidates(currentWord, MAX_CANDIDATES);
514
+ var existingWords = new Set(candidates.map(function (c) {
515
+ return c.word;
516
+ }));
517
+ var _iterator0 = _createForOfIteratorHelper(l3Candidates),
518
+ _step0;
519
+ try {
520
+ for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
521
+ var l3c = _step0.value;
522
+ if (candidates.length >= MAX_CANDIDATES) break; // Stop exactly at the limit
523
+
524
+ if (!existingWords.has(l3c.word)) {
525
+ candidates.push(l3c);
526
+ }
527
+ }
528
+ } catch (err) {
529
+ _iterator0.e(err);
530
+ } finally {
531
+ _iterator0.f();
532
+ }
533
+ }
534
+
535
+ // If both Tries are completely empty for this prefix
536
+ if (candidates.length === 0) {
537
+ return null;
538
+ }
539
+ var previousWord = extractPreviousWord(trimmed);
540
+ var contextVector = getContextVectorForScoring(trimmed);
541
+ var lmLogits = (0, _slowLaneClient.getStoredLmLogits)();
542
+
543
+ // Raw LM Output Logger
544
+ if (debugMode && lmLogits && Object.keys(lmLogits).length > 0) {
545
+ var rawLmTop = Object.entries(lmLogits).sort(function (a, b) {
546
+ return b[1] - a[1];
547
+ }).slice(0, 5).map(function (_ref) {
548
+ var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
549
+ word = _ref2[0],
550
+ score = _ref2[1];
551
+ return {
552
+ Word: word,
553
+ Prob: Number(score.toFixed(5))
554
+ };
555
+ });
556
+ // eslint-disable-next-line no-console
557
+ console.log('%c[Raw LM Prediction]🧠', 'color: #e83e8c; font-weight: bold;', rawLmTop);
558
+ }
559
+ var mode = contextVector ? 'warm' : 'cold';
560
+ if (debugMode && contextVector && !hasLoggedSemanticActive) {
561
+ hasLoggedSemanticActive = true;
562
+ }
563
+
564
+ // Build ScoringCandidate array from TrieNodes
565
+ var scoringCandidates = candidates.map(function (_ref3) {
566
+ var word = _ref3.word,
567
+ node = _ref3.node;
568
+ return {
569
+ word: word,
570
+ tenantFreq: node.tenantFreq,
571
+ docFreq: node.docFreq,
572
+ authorFreq: node.authorFreq,
573
+ sessionFreq: node.sessionFreq
574
+ };
575
+ });
576
+ var _rankCandidates = (0, _scoringPipeline.rankCandidates)(scoringCandidates, contextVector, function (w) {
577
+ return getWordVector(w);
578
+ }, lmLogits, wordTrie.maxTenantFreq, previousWord),
579
+ ranked = _rankCandidates.candidates,
580
+ grammarMeta = _rankCandidates.grammarMeta;
581
+ var best = ranked[0];
582
+ var suggestion = best && best.finalScore >= MIN_SCORE_THRESHOLD ? best.word.slice(currentWord.length) : null;
583
+ if (debugMode) {
584
+ var latencyMs = performance.now() - t0;
585
+ var tokens = tokenize(trimmed);
586
+ var contextWords = tokens.slice(-CONTEXT_WORDS);
587
+ var belowThreshold = best && best.finalScore < MIN_SCORE_THRESHOLD;
588
+ var suggestionLabel = belowThreshold ? '🚫 (below threshold)' : suggestion && suggestion.length > 0 ? "\u2728 \"".concat(suggestion, "\"") : '🚫 (no match)';
589
+ lastPredictionDebug = {
590
+ textBefore: trimmed,
591
+ currentWord: currentWord,
592
+ mode: mode,
593
+ contextWords: contextWords,
594
+ topCandidates: ranked.slice(0, 5).map(function (r) {
595
+ return {
596
+ word: r.word,
597
+ finalScore: r.finalScore,
598
+ semanticScore: r.semanticScore,
599
+ freqScore: r.freqScore,
600
+ lmScore: r.lmScore
601
+ };
602
+ }),
603
+ suggestion: suggestion && suggestion.length > 0 ? suggestion : null
604
+ };
605
+
606
+ // ── Mode label: COLD / WARM(local) / WARM(BE) ───────────────────────
607
+ var slowLaneVec = (0, _slowLaneClient.getStoredContextVector)();
608
+ var isUsingSlowLaneVector = slowLaneVec !== null && vectorStore !== null && slowLaneVec.length === vectorStore.dim;
609
+ var modeLabel = !contextVector ? 'COLD' : isUsingSlowLaneVector ? 'WARM(BE)' : 'WARM(local)';
610
+ var modeColor = modeLabel === 'WARM(BE)' ? 'color: #ff9800; font-weight: bold;' : modeLabel === 'WARM(local)' ? 'color: #4caf50; font-weight: bold;' : 'color: #9e9e9e; font-weight: bold;';
611
+
612
+ // 1. Collapsible group header
613
+ // eslint-disable-next-line no-console
614
+ console.groupCollapsed("%c[Autocomplete] %c".concat(modeLabel, " %c| \"").concat(currentWord, "\" \u2794 ").concat(suggestionLabel, " | \u23F1 ").concat(latencyMs.toFixed(1), "ms"), 'color: #00b8d9; font-weight: bold;', modeColor, 'color: inherit; font-weight: normal;');
615
+
616
+ // 2. Context window (what local vector averaging sees)
617
+ // eslint-disable-next-line no-console
618
+ console.log('%cContext Window:', 'color: #888; font-style: italic;', contextWords.length ? contextWords.join(' ') : '(none)');
619
+
620
+ // 3. Slow-lane status
621
+ var vectorStatus = isUsingSlowLaneVector ? "\u2705 BE semantic vector (dim=".concat(slowLaneVec === null || slowLaneVec === void 0 ? void 0 : slowLaneVec.length, ")") : vectorStore ? '⚠️ Local vector average (slow-lane not yet returned)' : '❌ No vectors (cold)';
622
+ var logitsStatus = lmLogits && Object.keys(lmLogits).length > 0 ? "\u2705 LM logits active (".concat(Object.keys(lmLogits).length, " tokens)") : '⏳ No LM logits (slow-lane pending or failed)';
623
+ // eslint-disable-next-line no-console
624
+ console.log('%cSlow Lane:', 'color: #888; font-style: italic;', vectorStatus, '|', logitsStatus);
625
+
626
+ // 4. Scoring formula active this prediction
627
+ var formulaLabel = lmLogits && Object.keys(lmLogits).length > 0 ? 'Stage1(×0.6) + LM(×0.4)' : 'Stage1 only (no LM logits)';
628
+ // eslint-disable-next-line no-console
629
+ console.log('%cFormula:', 'color: #888; font-style: italic;', formulaLabel);
630
+
631
+ // 5. Grammar filter result (collected inside rankCandidates, logged here)
632
+ if (grammarMeta) {
633
+ // eslint-disable-next-line no-console
634
+ console.log("%c[Grammar] \"".concat(grammarMeta.prevWord, "\" [").concat(grammarMeta.prevTags.join('|'), "] \u2192 ").concat(grammarMeta.before, " candidates \u2192 ").concat(grammarMeta.after, " after filter"), 'color: #4caf50; font-weight: bold;');
635
+ if (grammarMeta.dropped.length > 0) {
636
+ // eslint-disable-next-line no-console
637
+ console.log("%c\uD83D\uDEAB Dropped: ".concat(grammarMeta.dropped.join(', ')), 'color: #f44336; font-style: italic;');
638
+ }
639
+ }
640
+
641
+ // 6. Candidate table
642
+ if (ranked.length > 0) {
643
+ var lmCoverage = ranked.slice(0, 10).filter(function (r) {
644
+ return r.lmScore > 0.05;
645
+ }).length;
646
+ // eslint-disable-next-line no-console
647
+ console.log("%cLM coverage: ".concat(lmCoverage, "/").concat(Math.min(ranked.length, 10), " candidates had real logit scores"), 'color: #888; font-style: italic;');
648
+ var tableData = ranked.slice(0, 10).map(function (r) {
649
+ var rawLogit = 'Not in Payload';
650
+ if (lmLogits) {
651
+ var val = lmLogits[r.word.toLowerCase()];
652
+ if (val !== undefined) {
653
+ rawLogit = Number(val.toFixed(5));
654
+ }
655
+ }
656
+ var original = scoringCandidates.find(function (sc) {
657
+ return sc.word === r.word;
658
+ });
659
+ var source = 'Unknown';
660
+ if (original) {
661
+ if (original.docFreq === 0 && original.tenantFreq === L3_BASELINE_FREQ) {
662
+ source = '🌍 L3 (Generic)';
663
+ } else if (original.sessionFreq > 0 && original.tenantFreq === 0) {
664
+ source = '👤 L1 (Session Only)';
665
+ } else {
666
+ source = '🏢 L2 (Domain)';
667
+ }
668
+ }
669
+ return {
670
+ Candidate: r.word,
671
+ Source: source,
672
+ 'Final Score': Number(r.finalScore.toFixed(4)),
673
+ Semantics: Number(r.semanticScore.toFixed(4)),
674
+ Freq: Number(r.freqScore.toFixed(4)),
675
+ 'LM Score': Number(r.lmScore.toFixed(4)),
676
+ 'Raw Logit': rawLogit,
677
+ 'Session Freq': (original === null || original === void 0 ? void 0 : original.sessionFreq) || 0
678
+ };
679
+ });
680
+
681
+ // eslint-disable-next-line no-console
682
+ console.table(tableData);
683
+ } else {
684
+ // eslint-disable-next-line no-console
685
+ console.log('No candidates found.');
686
+ }
687
+
688
+ // eslint-disable-next-line no-console
689
+ console.groupEnd();
690
+ }
691
+ return suggestion && suggestion.length > 0 ? suggestion : null;
692
+ };
693
+
694
+ // ─── Data Loading ────────────────────────────────────────────────────────────
695
+
696
+ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
697
+ var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(options) {
698
+ var url, res, buffer, float32, wordIndex, nWords, dim;
699
+ return _regenerator.default.wrap(function _callee$(_context) {
700
+ while (1) switch (_context.prev = _context.next) {
701
+ case 0:
702
+ if (!(vectorStore || vectorsLoadStarted)) {
703
+ _context.next = 2;
704
+ break;
705
+ }
706
+ return _context.abrupt("return");
707
+ case 2:
708
+ if (options !== null && options !== void 0 && options.vectorsUrl) {
709
+ _context.next = 5;
710
+ break;
711
+ }
712
+ // eslint-disable-next-line no-console
713
+ console.warn('[text-predictor] loadVectorsAsync called without a vectorsUrl — vectors will not load. Pass vectorsUrl via plugin options.');
714
+ return _context.abrupt("return");
715
+ case 5:
716
+ vectorsLoadStarted = true;
717
+ url = options.vectorsUrl;
718
+ _context.prev = 7;
719
+ _context.next = 10;
720
+ return fetch(url);
721
+ case 10:
722
+ res = _context.sent;
723
+ if (res.ok) {
724
+ _context.next = 15;
725
+ break;
726
+ }
727
+ vectorsLoadStarted = false;
728
+ // eslint-disable-next-line no-console
729
+ console.warn("[text-predictor] Failed to load vectors: ".concat(res.status));
730
+ return _context.abrupt("return");
731
+ case 15:
732
+ _context.next = 17;
733
+ return res.arrayBuffer();
734
+ case 17:
735
+ buffer = _context.sent;
736
+ float32 = new Float32Array(buffer);
737
+ wordIndex = _word_index_10k.default;
738
+ nWords = Object.keys(wordIndex).length;
739
+ dim = float32.length / nWords;
740
+ vectorStore = {
741
+ float32: float32,
742
+ wordIndex: wordIndex,
743
+ dim: dim
744
+ };
745
+ ensureDebugModeFromStorage();
746
+ if (debugMode) {
747
+ // eslint-disable-next-line no-console
748
+ console.log('[text-predictor] Vectors loaded:', {
749
+ wordCount: nWords,
750
+ dim: dim,
751
+ sizeBytes: float32.byteLength
752
+ });
753
+ }
754
+ _context.next = 31;
755
+ break;
756
+ case 27:
757
+ _context.prev = 27;
758
+ _context.t0 = _context["catch"](7);
759
+ vectorsLoadStarted = false;
760
+ // eslint-disable-next-line no-console
761
+ console.warn('[text-predictor] Failed to load vectors:', _context.t0);
762
+ case 31:
763
+ case "end":
764
+ return _context.stop();
765
+ }
766
+ }, _callee, null, [[7, 27]]);
767
+ }));
768
+ return function loadVectorsAsync(_x) {
769
+ return _ref4.apply(this, arguments);
770
+ };
771
+ }();
772
+ var initVectors = exports.initVectors = function initVectors(store) {
773
+ vectorStore = store;
774
+ };
775
+ var loadDefaultVocabulary = exports.loadDefaultVocabulary = function loadDefaultVocabulary() {
776
+ // 1. Load the Atlassian Domain (L2)
777
+ var data = _vocabulary_10k.default;
778
+ var terms = Object.entries(data.words).map(function (_ref5) {
779
+ var _ref6 = (0, _slicedToArray2.default)(_ref5, 2),
780
+ word = _ref6[0],
781
+ stats = _ref6[1];
782
+ return {
783
+ word: word,
784
+ freq: stats.freq,
785
+ docFreq: stats.doc_freq,
786
+ authorFreq: stats.author_freq
787
+ };
788
+ });
789
+ initVocabulary({
790
+ terms: terms
791
+ });
792
+
793
+ // 2. Load General English (L3)
794
+ var l3Words = _l3_vocabulary.default;
795
+ initL3Vocabulary(l3Words);
796
+ };