@atlaskit/editor-plugin-autocomplete 2.2.0 → 2.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 (30) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/afm-cc/tsconfig.json +0 -1
  3. package/afm-products/tsconfig.json +0 -1
  4. package/build/tsconfig.json +0 -1
  5. package/dist/cjs/pm-plugins/autocomplete-plugin.js +41 -14
  6. package/dist/cjs/pm-plugins/debug-mode.js +24 -0
  7. package/dist/cjs/pm-plugins/slow-lane-client.js +42 -35
  8. package/dist/cjs/pm-plugins/text-predictor.js +12 -32
  9. package/dist/es2019/pm-plugins/autocomplete-plugin.js +41 -14
  10. package/dist/es2019/pm-plugins/debug-mode.js +18 -0
  11. package/dist/es2019/pm-plugins/slow-lane-client.js +29 -23
  12. package/dist/es2019/pm-plugins/text-predictor.js +6 -26
  13. package/dist/esm/pm-plugins/autocomplete-plugin.js +41 -14
  14. package/dist/esm/pm-plugins/debug-mode.js +18 -0
  15. package/dist/esm/pm-plugins/slow-lane-client.js +43 -35
  16. package/dist/esm/pm-plugins/text-predictor.js +11 -31
  17. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +1 -1
  18. package/dist/types/pm-plugins/debug-mode.d.ts +12 -0
  19. package/dist/types/pm-plugins/text-predictor.d.ts +0 -2
  20. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +1 -1
  21. package/dist/types-ts4.5/pm-plugins/debug-mode.d.ts +12 -0
  22. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +0 -2
  23. package/package.json +2 -2
  24. package/src/pm-plugins/autocomplete-plugin.ts +48 -12
  25. package/src/pm-plugins/debug-mode.ts +18 -0
  26. package/src/pm-plugins/slow-lane-client.ts +53 -45
  27. package/src/pm-plugins/text-predictor.ts +6 -30
  28. package/tsconfig.app.json +3 -1
  29. package/tsconfig.dev.json +2 -1
  30. package/afm-jira/tsconfig.json +0 -30
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @atlaskit/editor-plugin-autocomplete
2
2
 
3
+ ## 2.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`975a31aae73f4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/975a31aae73f4) -
8
+ Add KSS attributes to contextual typeahead acceptance analytics
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies
13
+
3
14
  ## 2.2.0
4
15
 
5
16
  ### Minor Changes
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../../tsconfig.local-consumption.json",
3
3
  "compilerOptions": {
4
- "target": "es5",
5
4
  "outDir": "../../../../../confluence/tsDist/@atlaskit__editor-plugin-autocomplete",
6
5
  "rootDir": "../",
7
6
  "composite": true,
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../../tsconfig.local-consumption.json",
3
3
  "compilerOptions": {
4
- "target": "es5",
5
4
  "outDir": "../../../../../tsDist/@atlaskit__editor-plugin-autocomplete/app",
6
5
  "rootDir": "../",
7
6
  "composite": true,
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../tsconfig",
3
3
  "compilerOptions": {
4
- "paths": {},
5
4
  "resolveJsonModule": true
6
5
  },
7
6
  "include": ["../src/**/*.ts", "../src/**/*.tsx"],
@@ -148,6 +148,16 @@ var buildSlowLaneText = function buildSlowLaneText(docText, context) {
148
148
  lines.push("reply ".concat(nextReplyNumber, ": ").concat(docText));
149
149
  return lines.join('\n');
150
150
  };
151
+ var getTypedLengthForPrediction = function getTypedLengthForPrediction(textBefore) {
152
+ var _textBefore$match$, _textBefore$match;
153
+ if ((0, _slowLaneClient.isWordBoundary)(textBefore)) {
154
+ return 0;
155
+ }
156
+
157
+ // eslint-disable-next-line require-unicode-regexp
158
+ var trailingToken = (_textBefore$match$ = (_textBefore$match = textBefore.match(/([^\s.,;:!?]*)$/)) === null || _textBefore$match === void 0 ? void 0 : _textBefore$match[1]) !== null && _textBefore$match$ !== void 0 ? _textBefore$match$ : '';
159
+ return trailingToken.length;
160
+ };
151
161
  var createAutocompletePlugin = exports.createAutocompletePlugin = function createAutocompletePlugin(options, api) {
152
162
  var debounceTimer = null;
153
163
  var hasIngestedPage = false;
@@ -165,6 +175,24 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
165
175
  * re-showing the same suggestion. Resets to null as soon as the text changes.
166
176
  */
167
177
  var dismissedContext = null;
178
+ var lastSuggestionTypedLength = 0;
179
+ var lastSuggestionLength = 0;
180
+ var fireSuggestionInsertedAnalytics = function fireSuggestionInsertedAnalytics(ghostText) {
181
+ var _api$analytics;
182
+ var typedLength = lastSuggestionTypedLength;
183
+ var suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
184
+ var kssDelta = suggestionLength - typedLength;
185
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
186
+ action: _analytics.ACTION.SUGGESTION_INSERTED,
187
+ actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
188
+ eventType: _analytics.EVENT_TYPE.TRACK,
189
+ attributes: {
190
+ suggestionLength: suggestionLength,
191
+ typedLength: typedLength,
192
+ kssDelta: kssDelta
193
+ }
194
+ });
195
+ };
168
196
  var slowLaneClient = (0, _slowLaneClient.createSlowLaneClient)({
169
197
  baseUrl: '',
170
198
  endpoint: SLOW_LANE_ENDPOINT
@@ -205,11 +233,14 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
205
233
  // Tier 1 prediction is synchronous -- no async needed
206
234
  var prediction = (0, _textPredictor.predict)(textBefore);
207
235
  if (prediction && prediction.length > 0) {
236
+ var typedLength = getTypedLengthForPrediction(textBefore);
237
+ lastSuggestionTypedLength = typedLength;
238
+ lastSuggestionLength = typedLength + prediction.length;
208
239
  showGhostText(view, prediction, selection.from);
209
240
  if (prediction !== lastShownGhostText) {
210
- var _api$analytics;
241
+ var _api$analytics2;
211
242
  lastShownGhostText = prediction;
212
- api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
243
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
213
244
  action: _analytics.ACTION.SUGGESTION_VIEWED,
214
245
  actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
215
246
  eventType: _analytics.EVENT_TYPE.TRACK
@@ -285,30 +316,26 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
285
316
  },
286
317
  handleKeyDown: (0, _keymap.keydownHandler)({
287
318
  Tab: function Tab(state, dispatch) {
319
+ var _pluginState$ghostTex;
320
+ var pluginState = autocompletePluginKey.getState(state);
321
+ var ghostText = (_pluginState$ghostTex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex !== void 0 ? _pluginState$ghostTex : '';
288
322
  var accepted = acceptGhostText(state, dispatch);
289
323
  if (accepted) {
290
- var _api$analytics2;
291
324
  justAccepted = true;
292
325
  lastShownGhostText = '';
293
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
294
- action: _analytics.ACTION.SUGGESTION_INSERTED,
295
- actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
296
- eventType: _analytics.EVENT_TYPE.TRACK
297
- });
326
+ fireSuggestionInsertedAnalytics(ghostText);
298
327
  }
299
328
  return accepted;
300
329
  },
301
330
  ArrowRight: function ArrowRight(state, dispatch) {
331
+ var _pluginState$ghostTex2;
332
+ var pluginState = autocompletePluginKey.getState(state);
333
+ var ghostText = (_pluginState$ghostTex2 = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex2 !== void 0 ? _pluginState$ghostTex2 : '';
302
334
  var accepted = acceptGhostText(state, dispatch);
303
335
  if (accepted) {
304
- var _api$analytics3;
305
336
  justAccepted = true;
306
337
  lastShownGhostText = '';
307
- api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.fireAnalyticsEvent({
308
- action: _analytics.ACTION.SUGGESTION_INSERTED,
309
- actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
310
- eventType: _analytics.EVENT_TYPE.TRACK
311
- });
338
+ fireSuggestionInsertedAnalytics(ghostText);
312
339
  }
313
340
  return accepted;
314
341
  },
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isAutocompleteDebugEnabled = void 0;
7
+ /**
8
+ * Contextual Typeahead Completions (CTC) debug logging utility.
9
+ *
10
+ * Logs are silent by default. To enable in any environment (dev, staging, prod):
11
+ *
12
+ * localStorage.setItem('atl-ctc-dbg', '1')
13
+ *
14
+ * Then reload the page. To disable:
15
+ *
16
+ * localStorage.removeItem('atl-ctc-dbg')
17
+ */
18
+ var isAutocompleteDebugEnabled = exports.isAutocompleteDebugEnabled = function isAutocompleteDebugEnabled() {
19
+ try {
20
+ return typeof localStorage !== 'undefined' && localStorage.getItem('atl-ctc-dbg') === '1';
21
+ } catch (_unused) {
22
+ return false;
23
+ }
24
+ };
@@ -8,6 +8,7 @@ exports.setDefaultSlowLaneClient = exports.isWordBoundary = exports.getStoredLmL
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
9
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
10
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
11
+ var _debugMode = require("./debug-mode");
11
12
  /**
12
13
  * Slow Lane Client: Backend context encoding for autocomplete.
13
14
  *
@@ -81,37 +82,41 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
81
82
  payload = {
82
83
  text: text,
83
84
  session_id: sessionId
84
- }; // Log the exact payload being sent so the comment/reply structure is visible
85
- // eslint-disable-next-line no-console
86
- console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE4 Sending context | ".concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
87
- text.split('\n').forEach(function (line, i, arr) {
85
+ };
86
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
88
87
  // eslint-disable-next-line no-console
89
- console.log(" ".concat(i === arr.length - 1 ? '▶' : ' ', " ").concat(line));
90
- });
91
- // eslint-disable-next-line no-console
92
- console.groupEnd();
93
- _context.prev = 7;
94
- _context.next = 10;
88
+ console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE4 Sending context | ".concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
89
+ text.split('\n').forEach(function (line, i, arr) {
90
+ // eslint-disable-next-line no-console
91
+ console.log(" ".concat(i === arr.length - 1 ? '▶' : ' ', " ").concat(line));
92
+ });
93
+ // eslint-disable-next-line no-console
94
+ console.groupEnd();
95
+ }
96
+ _context.prev = 5;
97
+ _context.next = 8;
95
98
  return fetchFn(url, {
96
99
  method: 'POST',
97
100
  headers: headers,
98
101
  body: JSON.stringify(payload)
99
102
  });
100
- case 10:
103
+ case 8:
101
104
  res = _context.sent;
102
105
  if (res.ok) {
103
- _context.next = 16;
106
+ _context.next = 14;
104
107
  break;
105
108
  }
106
109
  storedContextVector = null;
107
110
  storedLmLogits = null;
108
- // eslint-disable-next-line no-console
109
- console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
111
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
112
+ // eslint-disable-next-line no-console
113
+ console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
114
+ }
110
115
  return _context.abrupt("return");
111
- case 16:
112
- _context.next = 18;
116
+ case 14:
117
+ _context.next = 16;
113
118
  return res.json();
114
- case 18:
119
+ case 16:
115
120
  data = _context.sent;
116
121
  if (data.semantic_vector && Array.isArray(data.semantic_vector)) {
117
122
  storedContextVector = new Float32Array(data.semantic_vector);
@@ -123,36 +128,38 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
123
128
  } else {
124
129
  storedLmLogits = null;
125
130
  }
126
-
127
- // Log what came back so it can be correlated with the next prediction group
128
- // eslint-disable-next-line no-console
129
- console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE5 Response received", 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
130
- // eslint-disable-next-line no-console
131
- console.log(storedContextVector ? "\u2705 semantic_vector: ".concat(storedContextVector.length, " dims") : '❌ No semantic_vector');
132
- // eslint-disable-next-line no-console
133
- console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
134
- // eslint-disable-next-line no-console
135
- console.groupEnd();
131
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
132
+ // eslint-disable-next-line no-console
133
+ console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE5 Response received", 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
134
+ // eslint-disable-next-line no-console
135
+ console.log(storedContextVector ? "\u2705 semantic_vector: ".concat(storedContextVector.length, " dims") : '❌ No semantic_vector');
136
+ // eslint-disable-next-line no-console
137
+ console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
138
+ // eslint-disable-next-line no-console
139
+ console.groupEnd();
140
+ }
136
141
  onUpdate === null || onUpdate === void 0 || onUpdate({
137
142
  textLength: text.length,
138
143
  hasVector: storedContextVector !== null,
139
144
  hasLmLogits: storedLmLogits !== null
140
145
  });
141
146
  // eslint-disable-next-line no-unused-vars
142
- _context.next = 33;
147
+ _context.next = 28;
143
148
  break;
144
- case 28:
145
- _context.prev = 28;
146
- _context.t0 = _context["catch"](7);
149
+ case 23:
150
+ _context.prev = 23;
151
+ _context.t0 = _context["catch"](5);
147
152
  storedContextVector = null;
148
153
  storedLmLogits = null;
149
- // eslint-disable-next-line no-console
150
- console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
151
- case 33:
154
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
155
+ // eslint-disable-next-line no-console
156
+ console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
157
+ }
158
+ case 28:
152
159
  case "end":
153
160
  return _context.stop();
154
161
  }
155
- }, _callee, null, [[7, 28]]);
162
+ }, _callee, null, [[5, 23]]);
156
163
  }));
157
164
  return function doUpdateContext(_x) {
158
165
  return _ref.apply(this, arguments);
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
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;
7
+ exports.predict = exports.loadVectorsAsync = exports.loadDefaultVocabulary = exports.initVocabulary = exports.initVectors = exports.initL3Vocabulary = exports.ingestDocumentPage = exports.incrementSessionFreq = exports.getPredictorStatus = exports.getLastPredictionDebug = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
9
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
@@ -14,6 +14,7 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
14
14
  var _l3_vocabulary = _interopRequireDefault(require("./data/l3_vocabulary.json"));
15
15
  var _vocabulary_10k = _interopRequireDefault(require("./data/vocabulary_10k.json"));
16
16
  var _word_index_10k = _interopRequireDefault(require("./data/word_index_10k.json"));
17
+ var _debugMode = require("./debug-mode");
17
18
  var _scoringPipeline = require("./scoring-pipeline");
18
19
  var _slowLaneClient = require("./slow-lane-client");
19
20
  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; } } }; }
@@ -226,7 +227,7 @@ var initL3Vocabulary = exports.initL3Vocabulary = function initL3Vocabulary(l3Wo
226
227
  } finally {
227
228
  _iterator5.f();
228
229
  }
229
- if (debugMode) {
230
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
230
231
  // eslint-disable-next-line no-console
231
232
  console.log("[text-predictor] L3 General English loaded: ".concat(l3Words.length, " words"));
232
233
  }
@@ -239,9 +240,7 @@ var initL3Vocabulary = exports.initL3Vocabulary = function initL3Vocabulary(l3Wo
239
240
  var isInitialized = false;
240
241
  var vectorStore = null;
241
242
  var vectorsLoadStarted = false;
242
- var debugMode = true;
243
243
  var lastPredictionDebug = null;
244
- var hasLoggedSemanticActive = false;
245
244
 
246
245
  /** Get vector for a word from the store. */
247
246
  var getWordVector = function getWordVector(word) {
@@ -347,25 +346,12 @@ var extractPreviousWord = function extractPreviousWord(text) {
347
346
 
348
347
  // ─── Debug Helpers ───────────────────────────────────────────────────────────
349
348
 
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
349
  /**
363
350
  * Get predictor status for debugging.
364
351
  * vectorsLoaded: true when semantic scoring is active
365
352
  * wordCount: number of words in vector store (0 if not loaded)
366
353
  */
367
354
  var getPredictorStatus = exports.getPredictorStatus = function getPredictorStatus() {
368
- ensureDebugModeFromStorage();
369
355
  return {
370
356
  vectorsLoaded: vectorStore !== null,
371
357
  wordCount: vectorStore ? Object.keys(vectorStore.wordIndex).length : 0,
@@ -379,7 +365,6 @@ var getPredictorStatus = exports.getPredictorStatus = function getPredictorStatu
379
365
  * Returns null if no prediction has run yet or debug was off.
380
366
  */
381
367
  var getLastPredictionDebug = exports.getLastPredictionDebug = function getLastPredictionDebug() {
382
- ensureDebugModeFromStorage();
383
368
  return lastPredictionDebug;
384
369
  };
385
370
  var initVocabulary = exports.initVocabulary = function initVocabulary(vocabulary) {
@@ -438,7 +423,7 @@ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPag
438
423
  } finally {
439
424
  _iterator9.f();
440
425
  }
441
- if (debugMode && validBoostedWords.size > 0) {
426
+ if ((0, _debugMode.isAutocompleteDebugEnabled)() && validBoostedWords.size > 0) {
442
427
  // eslint-disable-next-line no-console
443
428
  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
429
  // eslint-disable-next-line no-console
@@ -448,7 +433,6 @@ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPag
448
433
  }
449
434
  };
450
435
  var predict = exports.predict = function predict(textBefore) {
451
- ensureDebugModeFromStorage();
452
436
  if (!isInitialized) {
453
437
  loadDefaultVocabulary();
454
438
  }
@@ -541,7 +525,7 @@ var predict = exports.predict = function predict(textBefore) {
541
525
  var lmLogits = (0, _slowLaneClient.getStoredLmLogits)();
542
526
 
543
527
  // Raw LM Output Logger
544
- if (debugMode && lmLogits && Object.keys(lmLogits).length > 0) {
528
+ if ((0, _debugMode.isAutocompleteDebugEnabled)() && lmLogits && Object.keys(lmLogits).length > 0) {
545
529
  var rawLmTop = Object.entries(lmLogits).sort(function (a, b) {
546
530
  return b[1] - a[1];
547
531
  }).slice(0, 5).map(function (_ref) {
@@ -557,9 +541,6 @@ var predict = exports.predict = function predict(textBefore) {
557
541
  console.log('%c[Raw LM Prediction]🧠', 'color: #e83e8c; font-weight: bold;', rawLmTop);
558
542
  }
559
543
  var mode = contextVector ? 'warm' : 'cold';
560
- if (debugMode && contextVector && !hasLoggedSemanticActive) {
561
- hasLoggedSemanticActive = true;
562
- }
563
544
 
564
545
  // Build ScoringCandidate array from TrieNodes
565
546
  var scoringCandidates = candidates.map(function (_ref3) {
@@ -590,7 +571,7 @@ var predict = exports.predict = function predict(textBefore) {
590
571
  pipelineDebug = _rankCandidates.pipelineDebug;
591
572
  var best = ranked[0];
592
573
  var suggestion = best && best.finalScore >= MIN_SCORE_THRESHOLD ? best.word.slice(currentWord.length) : null;
593
- if (debugMode) {
574
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
594
575
  var latencyMs = performance.now() - t0;
595
576
  var tokens = tokenize(trimmed);
596
577
  var contextWords = tokens.slice(-CONTEXT_WORDS);
@@ -770,8 +751,7 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
770
751
  wordIndex: wordIndex,
771
752
  dim: dim
772
753
  };
773
- ensureDebugModeFromStorage();
774
- if (debugMode) {
754
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
775
755
  // eslint-disable-next-line no-console
776
756
  console.log('[text-predictor] Vectors loaded:', {
777
757
  wordCount: nWords,
@@ -779,19 +759,19 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
779
759
  sizeBytes: float32.byteLength
780
760
  });
781
761
  }
782
- _context.next = 41;
762
+ _context.next = 40;
783
763
  break;
784
- case 37:
785
- _context.prev = 37;
764
+ case 36:
765
+ _context.prev = 36;
786
766
  _context.t1 = _context["catch"](17);
787
767
  vectorsLoadStarted = false;
788
768
  // eslint-disable-next-line no-console
789
769
  console.warn('[text-predictor] Failed to load vectors:', _context.t1);
790
- case 41:
770
+ case 40:
791
771
  case "end":
792
772
  return _context.stop();
793
773
  }
794
- }, _callee, null, [[6, 12], [17, 37]]);
774
+ }, _callee, null, [[6, 12], [17, 36]]);
795
775
  }));
796
776
  return function loadVectorsAsync(_x) {
797
777
  return _ref6.apply(this, arguments);
@@ -142,6 +142,16 @@ const buildSlowLaneText = (docText, context) => {
142
142
  lines.push(`reply ${nextReplyNumber}: ${docText}`);
143
143
  return lines.join('\n');
144
144
  };
145
+ const getTypedLengthForPrediction = textBefore => {
146
+ var _textBefore$match$, _textBefore$match;
147
+ if (isWordBoundary(textBefore)) {
148
+ return 0;
149
+ }
150
+
151
+ // eslint-disable-next-line require-unicode-regexp
152
+ const trailingToken = (_textBefore$match$ = (_textBefore$match = textBefore.match(/([^\s.,;:!?]*)$/)) === null || _textBefore$match === void 0 ? void 0 : _textBefore$match[1]) !== null && _textBefore$match$ !== void 0 ? _textBefore$match$ : '';
153
+ return trailingToken.length;
154
+ };
145
155
  export const createAutocompletePlugin = (options, api) => {
146
156
  let debounceTimer = null;
147
157
  let hasIngestedPage = false;
@@ -159,6 +169,24 @@ export const createAutocompletePlugin = (options, api) => {
159
169
  * re-showing the same suggestion. Resets to null as soon as the text changes.
160
170
  */
161
171
  let dismissedContext = null;
172
+ let lastSuggestionTypedLength = 0;
173
+ let lastSuggestionLength = 0;
174
+ const fireSuggestionInsertedAnalytics = ghostText => {
175
+ var _api$analytics;
176
+ const typedLength = lastSuggestionTypedLength;
177
+ const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
178
+ const kssDelta = suggestionLength - typedLength;
179
+ api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
180
+ action: ACTION.SUGGESTION_INSERTED,
181
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
182
+ eventType: EVENT_TYPE.TRACK,
183
+ attributes: {
184
+ suggestionLength,
185
+ typedLength,
186
+ kssDelta
187
+ }
188
+ });
189
+ };
162
190
  const slowLaneClient = createSlowLaneClient({
163
191
  baseUrl: '',
164
192
  endpoint: SLOW_LANE_ENDPOINT
@@ -203,11 +231,14 @@ export const createAutocompletePlugin = (options, api) => {
203
231
  // Tier 1 prediction is synchronous -- no async needed
204
232
  const prediction = predict(textBefore);
205
233
  if (prediction && prediction.length > 0) {
234
+ const typedLength = getTypedLengthForPrediction(textBefore);
235
+ lastSuggestionTypedLength = typedLength;
236
+ lastSuggestionLength = typedLength + prediction.length;
206
237
  showGhostText(view, prediction, selection.from);
207
238
  if (prediction !== lastShownGhostText) {
208
- var _api$analytics;
239
+ var _api$analytics2;
209
240
  lastShownGhostText = prediction;
210
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
241
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
211
242
  action: ACTION.SUGGESTION_VIEWED,
212
243
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
213
244
  eventType: EVENT_TYPE.TRACK
@@ -286,30 +317,26 @@ export const createAutocompletePlugin = (options, api) => {
286
317
  },
287
318
  handleKeyDown: keydownHandler({
288
319
  Tab: (state, dispatch) => {
320
+ var _pluginState$ghostTex;
321
+ const pluginState = autocompletePluginKey.getState(state);
322
+ const ghostText = (_pluginState$ghostTex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex !== void 0 ? _pluginState$ghostTex : '';
289
323
  const accepted = acceptGhostText(state, dispatch);
290
324
  if (accepted) {
291
- var _api$analytics2;
292
325
  justAccepted = true;
293
326
  lastShownGhostText = '';
294
- api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
295
- action: ACTION.SUGGESTION_INSERTED,
296
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
297
- eventType: EVENT_TYPE.TRACK
298
- });
327
+ fireSuggestionInsertedAnalytics(ghostText);
299
328
  }
300
329
  return accepted;
301
330
  },
302
331
  ArrowRight: (state, dispatch) => {
332
+ var _pluginState$ghostTex2;
333
+ const pluginState = autocompletePluginKey.getState(state);
334
+ const ghostText = (_pluginState$ghostTex2 = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex2 !== void 0 ? _pluginState$ghostTex2 : '';
303
335
  const accepted = acceptGhostText(state, dispatch);
304
336
  if (accepted) {
305
- var _api$analytics3;
306
337
  justAccepted = true;
307
338
  lastShownGhostText = '';
308
- api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.fireAnalyticsEvent({
309
- action: ACTION.SUGGESTION_INSERTED,
310
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
311
- eventType: EVENT_TYPE.TRACK
312
- });
339
+ fireSuggestionInsertedAnalytics(ghostText);
313
340
  }
314
341
  return accepted;
315
342
  },
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Contextual Typeahead Completions (CTC) debug logging utility.
3
+ *
4
+ * Logs are silent by default. To enable in any environment (dev, staging, prod):
5
+ *
6
+ * localStorage.setItem('atl-ctc-dbg', '1')
7
+ *
8
+ * Then reload the page. To disable:
9
+ *
10
+ * localStorage.removeItem('atl-ctc-dbg')
11
+ */
12
+ export const isAutocompleteDebugEnabled = () => {
13
+ try {
14
+ return typeof localStorage !== 'undefined' && localStorage.getItem('atl-ctc-dbg') === '1';
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
@@ -7,6 +7,8 @@
7
7
  * Response: { semantic_vector: number[], lm_logits: Record<string, number> }
8
8
  */
9
9
 
10
+ import { isAutocompleteDebugEnabled } from './debug-mode';
11
+
10
12
  // ─── Types ───────────────────────────────────────────────────────────────────
11
13
 
12
14
  /** Request payload for typeahead-encodings endpoint. */
@@ -63,16 +65,16 @@ export const createSlowLaneClient = config => {
63
65
  text,
64
66
  session_id: sessionId
65
67
  };
66
-
67
- // Log the exact payload being sent so the comment/reply structure is visible
68
- // eslint-disable-next-line no-console
69
- console.groupCollapsed(`%c[SlowLane] %c📤 Sending context | ${text.length} chars`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
70
- text.split('\n').forEach((line, i, arr) => {
68
+ if (isAutocompleteDebugEnabled()) {
69
+ // eslint-disable-next-line no-console
70
+ console.groupCollapsed(`%c[SlowLane] %c📤 Sending context | ${text.length} chars`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
71
+ text.split('\n').forEach((line, i, arr) => {
72
+ // eslint-disable-next-line no-console
73
+ console.log(` ${i === arr.length - 1 ? '▶' : ' '} ${line}`);
74
+ });
71
75
  // eslint-disable-next-line no-console
72
- console.log(` ${i === arr.length - 1 ? '▶' : ' '} ${line}`);
73
- });
74
- // eslint-disable-next-line no-console
75
- console.groupEnd();
76
+ console.groupEnd();
77
+ }
76
78
  try {
77
79
  const res = await fetchFn(url, {
78
80
  method: 'POST',
@@ -82,8 +84,10 @@ export const createSlowLaneClient = config => {
82
84
  if (!res.ok) {
83
85
  storedContextVector = null;
84
86
  storedLmLogits = null;
85
- // eslint-disable-next-line no-console
86
- console.log(`%c[SlowLane] %c❌ Request failed (${res.status})`, 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
87
+ if (isAutocompleteDebugEnabled()) {
88
+ // eslint-disable-next-line no-console
89
+ console.log(`%c[SlowLane] %c❌ Request failed (${res.status})`, 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
90
+ }
87
91
  return;
88
92
  }
89
93
  const data = await res.json();
@@ -97,16 +101,16 @@ export const createSlowLaneClient = config => {
97
101
  } else {
98
102
  storedLmLogits = null;
99
103
  }
100
-
101
- // Log what came back so it can be correlated with the next prediction group
102
- // eslint-disable-next-line no-console
103
- console.groupCollapsed(`%c[SlowLane] %c📥 Response received`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
104
- // eslint-disable-next-line no-console
105
- console.log(storedContextVector ? `✅ semantic_vector: ${storedContextVector.length} dims` : '❌ No semantic_vector');
106
- // eslint-disable-next-line no-console
107
- console.log(storedLmLogits ? `✅ lm_logits: ${Object.keys(storedLmLogits).length} tokens` : '❌ No lm_logits');
108
- // eslint-disable-next-line no-console
109
- console.groupEnd();
104
+ if (isAutocompleteDebugEnabled()) {
105
+ // eslint-disable-next-line no-console
106
+ console.groupCollapsed(`%c[SlowLane] %c📥 Response received`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
107
+ // eslint-disable-next-line no-console
108
+ console.log(storedContextVector ? `✅ semantic_vector: ${storedContextVector.length} dims` : '❌ No semantic_vector');
109
+ // eslint-disable-next-line no-console
110
+ console.log(storedLmLogits ? `✅ lm_logits: ${Object.keys(storedLmLogits).length} tokens` : '❌ No lm_logits');
111
+ // eslint-disable-next-line no-console
112
+ console.groupEnd();
113
+ }
110
114
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate({
111
115
  textLength: text.length,
112
116
  hasVector: storedContextVector !== null,
@@ -116,8 +120,10 @@ export const createSlowLaneClient = config => {
116
120
  } catch (e) {
117
121
  storedContextVector = null;
118
122
  storedLmLogits = null;
119
- // eslint-disable-next-line no-console
120
- console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
123
+ if (isAutocompleteDebugEnabled()) {
124
+ // eslint-disable-next-line no-console
125
+ console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
126
+ }
121
127
  }
122
128
  };
123
129
  const updateContextDebounced = text => {