@atlaskit/editor-plugin-autocomplete 2.1.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 (37) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/afm-cc/tsconfig.json +3 -1
  3. package/afm-products/tsconfig.json +3 -1
  4. package/build/tsconfig.json +0 -1
  5. package/dist/cjs/autocompletePlugin.js +3 -2
  6. package/dist/cjs/pm-plugins/autocomplete-plugin.js +59 -3
  7. package/dist/cjs/pm-plugins/debug-mode.js +24 -0
  8. package/dist/cjs/pm-plugins/slow-lane-client.js +42 -35
  9. package/dist/cjs/pm-plugins/text-predictor.js +12 -32
  10. package/dist/es2019/autocompletePlugin.js +3 -2
  11. package/dist/es2019/pm-plugins/autocomplete-plugin.js +59 -3
  12. package/dist/es2019/pm-plugins/debug-mode.js +18 -0
  13. package/dist/es2019/pm-plugins/slow-lane-client.js +29 -23
  14. package/dist/es2019/pm-plugins/text-predictor.js +6 -26
  15. package/dist/esm/autocompletePlugin.js +3 -2
  16. package/dist/esm/pm-plugins/autocomplete-plugin.js +59 -3
  17. package/dist/esm/pm-plugins/debug-mode.js +18 -0
  18. package/dist/esm/pm-plugins/slow-lane-client.js +43 -35
  19. package/dist/esm/pm-plugins/text-predictor.js +11 -31
  20. package/dist/types/autocompletePluginType.d.ts +3 -1
  21. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +4 -2
  22. package/dist/types/pm-plugins/debug-mode.d.ts +12 -0
  23. package/dist/types/pm-plugins/text-predictor.d.ts +0 -2
  24. package/dist/types-ts4.5/autocompletePluginType.d.ts +5 -1
  25. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +4 -2
  26. package/dist/types-ts4.5/pm-plugins/debug-mode.d.ts +12 -0
  27. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +0 -2
  28. package/package.json +3 -2
  29. package/src/autocompletePlugin.tsx +2 -2
  30. package/src/autocompletePluginType.ts +3 -1
  31. package/src/pm-plugins/autocomplete-plugin.ts +70 -4
  32. package/src/pm-plugins/debug-mode.ts +18 -0
  33. package/src/pm-plugins/slow-lane-client.ts +53 -45
  34. package/src/pm-plugins/text-predictor.ts +12 -34
  35. package/tsconfig.app.json +6 -1
  36. package/tsconfig.dev.json +2 -1
  37. package/afm-jira/tsconfig.json +0 -30
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
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
+
14
+ ## 2.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [`6eb5747f5ba37`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6eb5747f5ba37) -
19
+ Add SAR analytics for autocomplete plugin
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+
3
25
  ## 2.1.0
4
26
 
5
27
  ### 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,
@@ -25,6 +24,9 @@
25
24
  },
26
25
  {
27
26
  "path": "../../editor-common/afm-cc/tsconfig.json"
27
+ },
28
+ {
29
+ "path": "../../editor-plugin-analytics/afm-cc/tsconfig.json"
28
30
  }
29
31
  ]
30
32
  }
@@ -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,
@@ -25,6 +24,9 @@
25
24
  },
26
25
  {
27
26
  "path": "../../editor-common/afm-products/tsconfig.json"
27
+ },
28
+ {
29
+ "path": "../../editor-plugin-analytics/afm-products/tsconfig.json"
28
30
  }
29
31
  ]
30
32
  }
@@ -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"],
@@ -6,7 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.autocompletePlugin = void 0;
7
7
  var _autocompletePlugin = require("./pm-plugins/autocomplete-plugin");
8
8
  var autocompletePlugin = exports.autocompletePlugin = function autocompletePlugin(_ref) {
9
- var options = _ref.config;
9
+ var options = _ref.config,
10
+ api = _ref.api;
10
11
  return {
11
12
  name: 'autocomplete',
12
13
  getSharedState: function getSharedState(editorState) {
@@ -19,7 +20,7 @@ var autocompletePlugin = exports.autocompletePlugin = function autocompletePlugi
19
20
  return [{
20
21
  name: 'autocomplete',
21
22
  plugin: function plugin() {
22
- return (0, _autocompletePlugin.createAutocompletePlugin)(options);
23
+ return (0, _autocompletePlugin.createAutocompletePlugin)(options, api);
23
24
  }
24
25
  }];
25
26
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.createAutocompletePlugin = exports.autocompletePluginKey = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _analytics = require("@atlaskit/editor-common/analytics");
9
10
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
10
11
  var _keymap = require("@atlaskit/editor-prosemirror/keymap");
11
12
  var _state = require("@atlaskit/editor-prosemirror/state");
@@ -68,6 +69,7 @@ var setAutocompleteMeta = function setAutocompleteMeta(tr, meta) {
68
69
  /**
69
70
  * Apply a ghost text suggestion to the editor state.
70
71
  */
72
+ var lastShownGhostText = '';
71
73
  var showGhostText = function showGhostText(view, text, position) {
72
74
  var state = view.state,
73
75
  dispatch = view.dispatch;
@@ -146,7 +148,17 @@ var buildSlowLaneText = function buildSlowLaneText(docText, context) {
146
148
  lines.push("reply ".concat(nextReplyNumber, ": ").concat(docText));
147
149
  return lines.join('\n');
148
150
  };
149
- var createAutocompletePlugin = exports.createAutocompletePlugin = function createAutocompletePlugin(options) {
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
+ };
161
+ var createAutocompletePlugin = exports.createAutocompletePlugin = function createAutocompletePlugin(options, api) {
150
162
  var debounceTimer = null;
151
163
  var hasIngestedPage = false;
152
164
  var resolvedContext;
@@ -163,6 +175,24 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
163
175
  * re-showing the same suggestion. Resets to null as soon as the text changes.
164
176
  */
165
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
+ };
166
196
  var slowLaneClient = (0, _slowLaneClient.createSlowLaneClient)({
167
197
  baseUrl: '',
168
198
  endpoint: SLOW_LANE_ENDPOINT
@@ -203,7 +233,19 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
203
233
  // Tier 1 prediction is synchronous -- no async needed
204
234
  var prediction = (0, _textPredictor.predict)(textBefore);
205
235
  if (prediction && prediction.length > 0) {
236
+ var typedLength = getTypedLengthForPrediction(textBefore);
237
+ lastSuggestionTypedLength = typedLength;
238
+ lastSuggestionLength = typedLength + prediction.length;
206
239
  showGhostText(view, prediction, selection.from);
240
+ if (prediction !== lastShownGhostText) {
241
+ var _api$analytics2;
242
+ lastShownGhostText = prediction;
243
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
244
+ action: _analytics.ACTION.SUGGESTION_VIEWED,
245
+ actionSubject: _analytics.ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
246
+ eventType: _analytics.EVENT_TYPE.TRACK
247
+ });
248
+ }
207
249
  }
208
250
  }, DEBOUNCE_MS);
209
251
  };
@@ -274,13 +316,27 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
274
316
  },
275
317
  handleKeyDown: (0, _keymap.keydownHandler)({
276
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 : '';
277
322
  var accepted = acceptGhostText(state, dispatch);
278
- if (accepted) justAccepted = true;
323
+ if (accepted) {
324
+ justAccepted = true;
325
+ lastShownGhostText = '';
326
+ fireSuggestionInsertedAnalytics(ghostText);
327
+ }
279
328
  return accepted;
280
329
  },
281
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 : '';
282
334
  var accepted = acceptGhostText(state, dispatch);
283
- if (accepted) justAccepted = true;
335
+ if (accepted) {
336
+ justAccepted = true;
337
+ lastShownGhostText = '';
338
+ fireSuggestionInsertedAnalytics(ghostText);
339
+ }
284
340
  return accepted;
285
341
  },
286
342
  Escape: function Escape(state, dispatch) {
@@ -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);
@@ -1,6 +1,7 @@
1
1
  import { autocompletePluginKey, createAutocompletePlugin } from './pm-plugins/autocomplete-plugin';
2
2
  export const autocompletePlugin = ({
3
- config: options
3
+ config: options,
4
+ api
4
5
  }) => {
5
6
  return {
6
7
  name: 'autocomplete',
@@ -13,7 +14,7 @@ export const autocompletePlugin = ({
13
14
  pmPlugins() {
14
15
  return [{
15
16
  name: 'autocomplete',
16
- plugin: () => createAutocompletePlugin(options)
17
+ plugin: () => createAutocompletePlugin(options, api)
17
18
  }];
18
19
  }
19
20
  };
@@ -1,3 +1,4 @@
1
+ import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
1
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
3
  import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
3
4
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
@@ -58,6 +59,7 @@ const setAutocompleteMeta = (tr, meta) => {
58
59
  /**
59
60
  * Apply a ghost text suggestion to the editor state.
60
61
  */
62
+ let lastShownGhostText = '';
61
63
  const showGhostText = (view, text, position) => {
62
64
  const {
63
65
  state,
@@ -140,7 +142,17 @@ const buildSlowLaneText = (docText, context) => {
140
142
  lines.push(`reply ${nextReplyNumber}: ${docText}`);
141
143
  return lines.join('\n');
142
144
  };
143
- export const createAutocompletePlugin = options => {
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
+ };
155
+ export const createAutocompletePlugin = (options, api) => {
144
156
  let debounceTimer = null;
145
157
  let hasIngestedPage = false;
146
158
  let resolvedContext;
@@ -157,6 +169,24 @@ export const createAutocompletePlugin = options => {
157
169
  * re-showing the same suggestion. Resets to null as soon as the text changes.
158
170
  */
159
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
+ };
160
190
  const slowLaneClient = createSlowLaneClient({
161
191
  baseUrl: '',
162
192
  endpoint: SLOW_LANE_ENDPOINT
@@ -201,7 +231,19 @@ export const createAutocompletePlugin = options => {
201
231
  // Tier 1 prediction is synchronous -- no async needed
202
232
  const prediction = predict(textBefore);
203
233
  if (prediction && prediction.length > 0) {
234
+ const typedLength = getTypedLengthForPrediction(textBefore);
235
+ lastSuggestionTypedLength = typedLength;
236
+ lastSuggestionLength = typedLength + prediction.length;
204
237
  showGhostText(view, prediction, selection.from);
238
+ if (prediction !== lastShownGhostText) {
239
+ var _api$analytics2;
240
+ lastShownGhostText = prediction;
241
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
242
+ action: ACTION.SUGGESTION_VIEWED,
243
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
244
+ eventType: EVENT_TYPE.TRACK
245
+ });
246
+ }
205
247
  }
206
248
  }, DEBOUNCE_MS);
207
249
  };
@@ -275,13 +317,27 @@ export const createAutocompletePlugin = options => {
275
317
  },
276
318
  handleKeyDown: keydownHandler({
277
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 : '';
278
323
  const accepted = acceptGhostText(state, dispatch);
279
- if (accepted) justAccepted = true;
324
+ if (accepted) {
325
+ justAccepted = true;
326
+ lastShownGhostText = '';
327
+ fireSuggestionInsertedAnalytics(ghostText);
328
+ }
280
329
  return accepted;
281
330
  },
282
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 : '';
283
335
  const accepted = acceptGhostText(state, dispatch);
284
- if (accepted) justAccepted = true;
336
+ if (accepted) {
337
+ justAccepted = true;
338
+ lastShownGhostText = '';
339
+ fireSuggestionInsertedAnalytics(ghostText);
340
+ }
285
341
  return accepted;
286
342
  },
287
343
  Escape: (state, dispatch) => {
@@ -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
+ };