@atlaskit/editor-plugin-autocomplete 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/editor-plugin-autocomplete
2
2
 
3
+ ## 4.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`9869d944172b6`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9869d944172b6) -
8
+ Instrument the local (LocalLLM) slow-lane client with the existing `slow-lane-fetch` UFO
9
+ experience, tagged with an `isLocalLLM` flag (matching `load-vectors`/`load-vocabulary`) so
10
+ on-device inference latency and success-rate feed the same FE Reliability SLO as the network
11
+ slow-lane fetch. The `isLocalLLM` flag is now also emitted on the abort path of both the local and
12
+ network clients.
13
+
14
+ ## 4.0.2
15
+
16
+ ### Patch Changes
17
+
18
+ - [`56263a9d5bb6d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/56263a9d5bb6d) -
19
+ Gate editor autocomplete to English locales so suggestions and model loading are disabled for
20
+ non-English users.
21
+
3
22
  ## 4.0.1
4
23
 
5
24
  ### Patch Changes
@@ -7,7 +7,8 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.succeedExp = exports.startExp = exports.failExp = exports.abortExp = exports.EXPERIENCE_NAME = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _ufo = require("@atlaskit/ufo");
10
- /**
10
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /**
11
12
  * UFO experience tracking helpers for `@atlaskit/editor-plugin-autocomplete`.
12
13
  *
13
14
  * Pattern follows `packages/linking-platform/smart-card/src/state/analytics/ufoExperiences.ts`
@@ -19,7 +20,6 @@ var _ufo = require("@atlaskit/ufo");
19
20
  * they can power FE Reliability SLOs:
20
21
  * https://hello.atlassian.net/wiki/spaces/AA6/pages/3961393753
21
22
  */
22
-
23
23
  /**
24
24
  * Experience name strings are surfaced downstream by the UFO pipeline as
25
25
  * `platform.fe.<type>.<platform.component>.<name>` (e.g.
@@ -95,15 +95,21 @@ var failExp = exports.failExp = function failExp(name, id, metadata) {
95
95
  // UFO errors must never break the plugin
96
96
  }
97
97
  };
98
- var abortExp = exports.abortExp = function abortExp(name, id, reason) {
98
+ var abortExp = exports.abortExp = function abortExp(name, id, reason, metadata) {
99
99
  if (!isUfoEnabled()) {
100
100
  return;
101
101
  }
102
102
  try {
103
- experiences[name].getInstance(id).abort(reason ? {
104
- metadata: {
105
- reason: reason
106
- }
103
+ // `reason` is the dedicated, canonical channel for the abort reason and
104
+ // intentionally takes precedence over a `reason` key in `metadata`. Callers
105
+ // must pass the reason via the param, not inside `metadata` (the `reason`
106
+ // key there is reserved and would be overwritten).
107
+ var merged = _objectSpread(_objectSpread({}, metadata), reason ? {
108
+ reason: reason
109
+ } : {});
110
+ var hasMetadata = Object.keys(merged).length > 0;
111
+ experiences[name].getInstance(id).abort(hasMetadata ? {
112
+ metadata: merged
107
113
  } : undefined);
108
114
  } catch (_unused4) {
109
115
  // UFO errors must never break the plugin
@@ -230,7 +230,16 @@ var getTypedLengthForPrediction = function getTypedLengthForPrediction(textBefor
230
230
  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$ : '';
231
231
  return trailingToken.length;
232
232
  };
233
+ var isEnglishLocale = function isEnglishLocale(locale) {
234
+ if (!locale) {
235
+ return false;
236
+ }
237
+ return locale.toLowerCase().startsWith('en');
238
+ };
233
239
  var createAutocompletePlugin = exports.createAutocompletePlugin = function createAutocompletePlugin(options, api) {
240
+ var _options$locale;
241
+ var locale = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : typeof navigator !== 'undefined' ? navigator.language : undefined;
242
+ var isAutocompleteEnabled = isEnglishLocale(locale);
234
243
  var debounceTimer = null;
235
244
  var hasIngestedPage = false;
236
245
  var resolvedContext;
@@ -620,6 +629,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
620
629
  }),
621
630
  handleDOMEvents: {
622
631
  blur: function blur(view) {
632
+ if (!isAutocompleteEnabled) {
633
+ return false;
634
+ }
623
635
  var pluginState = autocompletePluginKey.getState(view.state);
624
636
  if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
625
637
  clearGhostText(view.state, view.dispatch);
@@ -629,6 +641,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
629
641
  },
630
642
  focus: function focus() {
631
643
  var _options$useLocalMode, _options$useLocalMode2;
644
+ if (!isAutocompleteEnabled) {
645
+ return false;
646
+ }
632
647
  (0, _textPredictor.loadDefaultVocabulary)({
633
648
  isLocalLLM: (_options$useLocalMode = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode !== void 0 ? _options$useLocalMode : false
634
649
  }).catch(function (error) {
@@ -658,6 +673,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
658
673
  view: function view() {
659
674
  return {
660
675
  update: function update(view, prevState) {
676
+ if (!isAutocompleteEnabled) {
677
+ return;
678
+ }
661
679
  currentView = view;
662
680
  if (!prevState.doc.eq(view.state.doc)) {
663
681
  if (justAccepted) {
@@ -14,6 +14,7 @@ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
14
14
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
15
15
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
16
16
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
17
+ var _ufo = require("../analytics/ufo");
17
18
  var _debugMode = require("./debug-mode");
18
19
  var _slowLaneClient = require("./slow-lane-client");
19
20
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -959,7 +960,7 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
959
960
  */
960
961
  var runInference = /*#__PURE__*/function () {
961
962
  var _ref0 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(text, requestId) {
962
- var lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
963
+ var experienceId, lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
963
964
  return _regenerator.default.wrap(function (_context4) {
964
965
  while (1) switch (_context4.prev = _context4.next) {
965
966
  case 0:
@@ -969,7 +970,7 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
969
970
  }
970
971
  return _context4.abrupt("return");
971
972
  case 1:
972
- // Clear the capture buffer so we read only this pass's logits. The engine
973
+ experienceId = String(requestId); // Clear the capture buffer so we read only this pass's logits. The engine
973
974
  // serialises per-model requests and updateContext is debounced, so the
974
975
  // latest request's decode step is the last to populate `captured` before
975
976
  // we read it below; stale requests bail on the latestRequestId guard.
@@ -995,6 +996,16 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
995
996
  console.log("%c[LocalSlowLane] %c\uD83E\uDDE0 LM input (".concat(lmText.length, " chars, ").concat(splitOnWhitespace(lmText).length, " words): \"").concat(lmText.length > 100 ? "".concat(lmText.slice(0, 100), "\u2026") : lmText, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
996
997
  }
997
998
  _context4.prev = 2;
999
+ // Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
1000
+ // matching LOAD_VECTORS/LOAD_VOCABULARY) so on-device inference
1001
+ // latency/success-rate feeds the same FE Reliability SLO. Started inside
1002
+ // the try — post engine-init (parity with the network fetch, which
1003
+ // excludes the one-time model load) and so the catch below always
1004
+ // terminates the experience, even on a synchronous throw.
1005
+ (0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1006
+ textLength: text.length,
1007
+ isLocalLLM: true
1008
+ });
998
1009
  tStart = performance.now();
999
1010
  tLmDone = 0;
1000
1011
  tEmbDone = 0;
@@ -1027,6 +1038,9 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
1027
1038
  _context4.next = 4;
1028
1039
  break;
1029
1040
  }
1041
+ (0, _ufo.abortExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
1042
+ isLocalLLM: true
1043
+ });
1030
1044
  return _context4.abrupt("return");
1031
1045
  case 4:
1032
1046
  // ── LM logits: whole-word BE-parity payload ──────────────────
@@ -1078,6 +1092,12 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
1078
1092
  // eslint-disable-next-line no-console
1079
1093
  console.groupEnd();
1080
1094
  }
1095
+ (0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1096
+ textLength: text.length,
1097
+ hasVector: storedContextVector !== null,
1098
+ hasLmLogits: storedLmLogits !== null,
1099
+ isLocalLLM: true
1100
+ });
1081
1101
  onUpdate === null || onUpdate === void 0 || onUpdate({
1082
1102
  textLength: text.length,
1083
1103
  hasVector: storedContextVector !== null,
@@ -1092,10 +1112,17 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
1092
1112
  _context4.next = 6;
1093
1113
  break;
1094
1114
  }
1115
+ (0, _ufo.abortExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
1116
+ isLocalLLM: true
1117
+ });
1095
1118
  return _context4.abrupt("return");
1096
1119
  case 6:
1097
1120
  storedContextVector = null;
1098
1121
  storedLmLogits = null;
1122
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1123
+ errorType: 'inference',
1124
+ isLocalLLM: true
1125
+ });
1099
1126
  onUpdate === null || onUpdate === void 0 || onUpdate({
1100
1127
  textLength: text.length,
1101
1128
  hasVector: false,
@@ -87,7 +87,8 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
87
87
  session_id: sessionId
88
88
  };
89
89
  (0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
90
- textLength: text.length
90
+ textLength: text.length,
91
+ isLocalLLM: false
91
92
  });
92
93
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
93
94
  // eslint-disable-next-line no-console
@@ -116,7 +117,8 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
116
117
  storedLmLogits = null;
117
118
  (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
118
119
  status: res.status,
119
- errorType: 'http_error'
120
+ errorType: 'http_error',
121
+ isLocalLLM: false
120
122
  });
121
123
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
122
124
  // eslint-disable-next-line no-console
@@ -151,7 +153,8 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
151
153
  (0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
152
154
  textLength: text.length,
153
155
  hasVector: storedContextVector !== null,
154
- hasLmLogits: storedLmLogits !== null
156
+ hasLmLogits: storedLmLogits !== null,
157
+ isLocalLLM: false
155
158
  });
156
159
  onUpdate === null || onUpdate === void 0 || onUpdate({
157
160
  textLength: text.length,
@@ -167,7 +170,8 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
167
170
  storedContextVector = null;
168
171
  storedLmLogits = null;
169
172
  (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
170
- errorType: 'network'
173
+ errorType: 'network',
174
+ isLocalLLM: false
171
175
  });
172
176
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
173
177
  // eslint-disable-next-line no-console
@@ -197,7 +201,9 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
197
201
  debounceTimer = setTimeout(function () {
198
202
  debounceTimer = null;
199
203
  if (inflightRequestId !== null) {
200
- (0, _ufo.abortExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded');
204
+ (0, _ufo.abortExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded', {
205
+ isLocalLLM: false
206
+ });
201
207
  }
202
208
  var requestId = String(++requestSeq);
203
209
  inflightRequestId = requestId;
@@ -94,15 +94,24 @@ export const failExp = (name, id, metadata) => {
94
94
  // UFO errors must never break the plugin
95
95
  }
96
96
  };
97
- export const abortExp = (name, id, reason) => {
97
+ export const abortExp = (name, id, reason, metadata) => {
98
98
  if (!isUfoEnabled()) {
99
99
  return;
100
100
  }
101
101
  try {
102
- experiences[name].getInstance(id).abort(reason ? {
103
- metadata: {
102
+ // `reason` is the dedicated, canonical channel for the abort reason and
103
+ // intentionally takes precedence over a `reason` key in `metadata`. Callers
104
+ // must pass the reason via the param, not inside `metadata` (the `reason`
105
+ // key there is reserved and would be overwritten).
106
+ const merged = {
107
+ ...metadata,
108
+ ...(reason ? {
104
109
  reason
105
- }
110
+ } : {})
111
+ };
112
+ const hasMetadata = Object.keys(merged).length > 0;
113
+ experiences[name].getInstance(id).abort(hasMetadata ? {
114
+ metadata: merged
106
115
  } : undefined);
107
116
  } catch {
108
117
  // UFO errors must never break the plugin
@@ -218,7 +218,16 @@ const getTypedLengthForPrediction = textBefore => {
218
218
  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$ : '';
219
219
  return trailingToken.length;
220
220
  };
221
+ const isEnglishLocale = locale => {
222
+ if (!locale) {
223
+ return false;
224
+ }
225
+ return locale.toLowerCase().startsWith('en');
226
+ };
221
227
  export const createAutocompletePlugin = (options, api) => {
228
+ var _options$locale;
229
+ const locale = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : typeof navigator !== 'undefined' ? navigator.language : undefined;
230
+ const isAutocompleteEnabled = isEnglishLocale(locale);
222
231
  let debounceTimer = null;
223
232
  let hasIngestedPage = false;
224
233
  let resolvedContext;
@@ -607,6 +616,9 @@ export const createAutocompletePlugin = (options, api) => {
607
616
  }),
608
617
  handleDOMEvents: {
609
618
  blur: view => {
619
+ if (!isAutocompleteEnabled) {
620
+ return false;
621
+ }
610
622
  const pluginState = autocompletePluginKey.getState(view.state);
611
623
  if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
612
624
  clearGhostText(view.state, view.dispatch);
@@ -616,6 +628,9 @@ export const createAutocompletePlugin = (options, api) => {
616
628
  },
617
629
  focus: () => {
618
630
  var _options$useLocalMode, _options$useLocalMode2;
631
+ if (!isAutocompleteEnabled) {
632
+ return false;
633
+ }
619
634
  loadDefaultVocabulary({
620
635
  isLocalLLM: (_options$useLocalMode = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode !== void 0 ? _options$useLocalMode : false
621
636
  }).catch(error => {
@@ -644,6 +659,9 @@ export const createAutocompletePlugin = (options, api) => {
644
659
  },
645
660
  view: () => ({
646
661
  update: (view, prevState) => {
662
+ if (!isAutocompleteEnabled) {
663
+ return;
664
+ }
647
665
  currentView = view;
648
666
  if (!prevState.doc.eq(view.state.doc)) {
649
667
  if (justAccepted) {
@@ -36,6 +36,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
36
36
  * asynchronously after each updateContext() call.
37
37
  */
38
38
 
39
+ import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
39
40
  import { isAutocompleteDebugEnabled } from './debug-mode';
40
41
  import { isWordBoundary } from './slow-lane-client';
41
42
 
@@ -748,6 +749,7 @@ export const createLocalSlowLaneClient = (config = {}) => {
748
749
  if (!engine || destroyed) {
749
750
  return;
750
751
  }
752
+ const experienceId = String(requestId);
751
753
 
752
754
  // Clear the capture buffer so we read only this pass's logits. The engine
753
755
  // serialises per-model requests and updateContext is debounced, so the
@@ -774,6 +776,16 @@ export const createLocalSlowLaneClient = (config = {}) => {
774
776
  }
775
777
  try {
776
778
  var _data, _data$;
779
+ // Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
780
+ // matching LOAD_VECTORS/LOAD_VOCABULARY) so on-device inference
781
+ // latency/success-rate feeds the same FE Reliability SLO. Started inside
782
+ // the try — post engine-init (parity with the network fetch, which
783
+ // excludes the one-time model load) and so the catch below always
784
+ // terminates the experience, even on a synchronous throw.
785
+ startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
786
+ textLength: text.length,
787
+ isLocalLLM: true
788
+ });
777
789
  const tStart = performance.now();
778
790
  let tLmDone = 0;
779
791
  let tEmbDone = 0;
@@ -798,6 +810,9 @@ export const createLocalSlowLaneClient = (config = {}) => {
798
810
 
799
811
  // Discard stale results
800
812
  if (requestId < latestRequestId || destroyed) {
813
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
814
+ isLocalLLM: true
815
+ });
801
816
  return;
802
817
  }
803
818
 
@@ -840,6 +855,12 @@ export const createLocalSlowLaneClient = (config = {}) => {
840
855
  // eslint-disable-next-line no-console
841
856
  console.groupEnd();
842
857
  }
858
+ succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
859
+ textLength: text.length,
860
+ hasVector: storedContextVector !== null,
861
+ hasLmLogits: storedLmLogits !== null,
862
+ isLocalLLM: true
863
+ });
843
864
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate({
844
865
  textLength: text.length,
845
866
  hasVector: storedContextVector !== null,
@@ -848,10 +869,17 @@ export const createLocalSlowLaneClient = (config = {}) => {
848
869
  } catch (err) {
849
870
  // Discard errors for stale requests or after teardown
850
871
  if (requestId < latestRequestId || destroyed) {
872
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
873
+ isLocalLLM: true
874
+ });
851
875
  return;
852
876
  }
853
877
  storedContextVector = null;
854
878
  storedLmLogits = null;
879
+ failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
880
+ errorType: 'inference',
881
+ isLocalLLM: true
882
+ });
855
883
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate({
856
884
  textLength: text.length,
857
885
  hasVector: false,
@@ -69,7 +69,8 @@ export const createSlowLaneClient = config => {
69
69
  session_id: sessionId
70
70
  };
71
71
  startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
72
- textLength: text.length
72
+ textLength: text.length,
73
+ isLocalLLM: false
73
74
  });
74
75
  if (isAutocompleteDebugEnabled()) {
75
76
  // eslint-disable-next-line no-console
@@ -92,7 +93,8 @@ export const createSlowLaneClient = config => {
92
93
  storedLmLogits = null;
93
94
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
94
95
  status: res.status,
95
- errorType: 'http_error'
96
+ errorType: 'http_error',
97
+ isLocalLLM: false
96
98
  });
97
99
  if (isAutocompleteDebugEnabled()) {
98
100
  // eslint-disable-next-line no-console
@@ -124,7 +126,8 @@ export const createSlowLaneClient = config => {
124
126
  succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
125
127
  textLength: text.length,
126
128
  hasVector: storedContextVector !== null,
127
- hasLmLogits: storedLmLogits !== null
129
+ hasLmLogits: storedLmLogits !== null,
130
+ isLocalLLM: false
128
131
  });
129
132
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate({
130
133
  textLength: text.length,
@@ -136,7 +139,8 @@ export const createSlowLaneClient = config => {
136
139
  storedContextVector = null;
137
140
  storedLmLogits = null;
138
141
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
139
- errorType: 'network'
142
+ errorType: 'network',
143
+ isLocalLLM: false
140
144
  });
141
145
  if (isAutocompleteDebugEnabled()) {
142
146
  // eslint-disable-next-line no-console
@@ -156,7 +160,9 @@ export const createSlowLaneClient = config => {
156
160
  debounceTimer = setTimeout(() => {
157
161
  debounceTimer = null;
158
162
  if (inflightRequestId !== null) {
159
- abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded');
163
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded', {
164
+ isLocalLLM: false
165
+ });
160
166
  }
161
167
  const requestId = String(++requestSeq);
162
168
  inflightRequestId = requestId;
@@ -1,4 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2
4
  /**
3
5
  * UFO experience tracking helpers for `@atlaskit/editor-plugin-autocomplete`.
4
6
  *
@@ -89,15 +91,21 @@ export var failExp = function failExp(name, id, metadata) {
89
91
  // UFO errors must never break the plugin
90
92
  }
91
93
  };
92
- export var abortExp = function abortExp(name, id, reason) {
94
+ export var abortExp = function abortExp(name, id, reason, metadata) {
93
95
  if (!isUfoEnabled()) {
94
96
  return;
95
97
  }
96
98
  try {
97
- experiences[name].getInstance(id).abort(reason ? {
98
- metadata: {
99
- reason: reason
100
- }
99
+ // `reason` is the dedicated, canonical channel for the abort reason and
100
+ // intentionally takes precedence over a `reason` key in `metadata`. Callers
101
+ // must pass the reason via the param, not inside `metadata` (the `reason`
102
+ // key there is reserved and would be overwritten).
103
+ var merged = _objectSpread(_objectSpread({}, metadata), reason ? {
104
+ reason: reason
105
+ } : {});
106
+ var hasMetadata = Object.keys(merged).length > 0;
107
+ experiences[name].getInstance(id).abort(hasMetadata ? {
108
+ metadata: merged
101
109
  } : undefined);
102
110
  } catch (_unused4) {
103
111
  // UFO errors must never break the plugin
@@ -223,7 +223,16 @@ var getTypedLengthForPrediction = function getTypedLengthForPrediction(textBefor
223
223
  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$ : '';
224
224
  return trailingToken.length;
225
225
  };
226
+ var isEnglishLocale = function isEnglishLocale(locale) {
227
+ if (!locale) {
228
+ return false;
229
+ }
230
+ return locale.toLowerCase().startsWith('en');
231
+ };
226
232
  export var createAutocompletePlugin = function createAutocompletePlugin(options, api) {
233
+ var _options$locale;
234
+ var locale = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : typeof navigator !== 'undefined' ? navigator.language : undefined;
235
+ var isAutocompleteEnabled = isEnglishLocale(locale);
227
236
  var debounceTimer = null;
228
237
  var hasIngestedPage = false;
229
238
  var resolvedContext;
@@ -613,6 +622,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
613
622
  }),
614
623
  handleDOMEvents: {
615
624
  blur: function blur(view) {
625
+ if (!isAutocompleteEnabled) {
626
+ return false;
627
+ }
616
628
  var pluginState = autocompletePluginKey.getState(view.state);
617
629
  if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
618
630
  clearGhostText(view.state, view.dispatch);
@@ -622,6 +634,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
622
634
  },
623
635
  focus: function focus() {
624
636
  var _options$useLocalMode, _options$useLocalMode2;
637
+ if (!isAutocompleteEnabled) {
638
+ return false;
639
+ }
625
640
  loadDefaultVocabulary({
626
641
  isLocalLLM: (_options$useLocalMode = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode !== void 0 ? _options$useLocalMode : false
627
642
  }).catch(function (error) {
@@ -651,6 +666,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
651
666
  view: function view() {
652
667
  return {
653
668
  update: function update(view, prevState) {
669
+ if (!isAutocompleteEnabled) {
670
+ return;
671
+ }
654
672
  currentView = view;
655
673
  if (!prevState.doc.eq(view.state.doc)) {
656
674
  if (justAccepted) {
@@ -48,6 +48,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
48
48
  * asynchronously after each updateContext() call.
49
49
  */
50
50
 
51
+ import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
51
52
  import { isAutocompleteDebugEnabled } from './debug-mode';
52
53
  import { isWordBoundary } from './slow-lane-client';
53
54
 
@@ -947,7 +948,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
947
948
  */
948
949
  var runInference = /*#__PURE__*/function () {
949
950
  var _ref0 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(text, requestId) {
950
- var lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
951
+ var experienceId, lmText, semanticText, arcticInput, captureCompletionTime, _data, tStart, tLmDone, tEmbDone, _yield$Promise$all5, _yield$Promise$all6, embeddingResponse, rawLogits, payload, embedding, sumSq, i, topTokens, errorMsg, _t5;
951
952
  return _regeneratorRuntime.wrap(function (_context4) {
952
953
  while (1) switch (_context4.prev = _context4.next) {
953
954
  case 0:
@@ -957,7 +958,7 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
957
958
  }
958
959
  return _context4.abrupt("return");
959
960
  case 1:
960
- // Clear the capture buffer so we read only this pass's logits. The engine
961
+ experienceId = String(requestId); // Clear the capture buffer so we read only this pass's logits. The engine
961
962
  // serialises per-model requests and updateContext is debounced, so the
962
963
  // latest request's decode step is the last to populate `captured` before
963
964
  // we read it below; stale requests bail on the latestRequestId guard.
@@ -983,6 +984,16 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
983
984
  console.log("%c[LocalSlowLane] %c\uD83E\uDDE0 LM input (".concat(lmText.length, " chars, ").concat(splitOnWhitespace(lmText).length, " words): \"").concat(lmText.length > 100 ? "".concat(lmText.slice(0, 100), "\u2026") : lmText, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #2196f3;');
984
985
  }
985
986
  _context4.prev = 2;
987
+ // Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
988
+ // matching LOAD_VECTORS/LOAD_VOCABULARY) so on-device inference
989
+ // latency/success-rate feeds the same FE Reliability SLO. Started inside
990
+ // the try — post engine-init (parity with the network fetch, which
991
+ // excludes the one-time model load) and so the catch below always
992
+ // terminates the experience, even on a synchronous throw.
993
+ startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
994
+ textLength: text.length,
995
+ isLocalLLM: true
996
+ });
986
997
  tStart = performance.now();
987
998
  tLmDone = 0;
988
999
  tEmbDone = 0;
@@ -1015,6 +1026,9 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1015
1026
  _context4.next = 4;
1016
1027
  break;
1017
1028
  }
1029
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
1030
+ isLocalLLM: true
1031
+ });
1018
1032
  return _context4.abrupt("return");
1019
1033
  case 4:
1020
1034
  // ── LM logits: whole-word BE-parity payload ──────────────────
@@ -1066,6 +1080,12 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1066
1080
  // eslint-disable-next-line no-console
1067
1081
  console.groupEnd();
1068
1082
  }
1083
+ succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1084
+ textLength: text.length,
1085
+ hasVector: storedContextVector !== null,
1086
+ hasLmLogits: storedLmLogits !== null,
1087
+ isLocalLLM: true
1088
+ });
1069
1089
  onUpdate === null || onUpdate === void 0 || onUpdate({
1070
1090
  textLength: text.length,
1071
1091
  hasVector: storedContextVector !== null,
@@ -1080,10 +1100,17 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
1080
1100
  _context4.next = 6;
1081
1101
  break;
1082
1102
  }
1103
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, destroyed ? 'destroyed' : 'superseded', {
1104
+ isLocalLLM: true
1105
+ });
1083
1106
  return _context4.abrupt("return");
1084
1107
  case 6:
1085
1108
  storedContextVector = null;
1086
1109
  storedLmLogits = null;
1110
+ failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1111
+ errorType: 'inference',
1112
+ isLocalLLM: true
1113
+ });
1087
1114
  onUpdate === null || onUpdate === void 0 || onUpdate({
1088
1115
  textLength: text.length,
1089
1116
  hasVector: false,
@@ -81,7 +81,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
81
81
  session_id: sessionId
82
82
  };
83
83
  startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
84
- textLength: text.length
84
+ textLength: text.length,
85
+ isLocalLLM: false
85
86
  });
86
87
  if (isAutocompleteDebugEnabled()) {
87
88
  // eslint-disable-next-line no-console
@@ -110,7 +111,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
110
111
  storedLmLogits = null;
111
112
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
112
113
  status: res.status,
113
- errorType: 'http_error'
114
+ errorType: 'http_error',
115
+ isLocalLLM: false
114
116
  });
115
117
  if (isAutocompleteDebugEnabled()) {
116
118
  // eslint-disable-next-line no-console
@@ -145,7 +147,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
145
147
  succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
146
148
  textLength: text.length,
147
149
  hasVector: storedContextVector !== null,
148
- hasLmLogits: storedLmLogits !== null
150
+ hasLmLogits: storedLmLogits !== null,
151
+ isLocalLLM: false
149
152
  });
150
153
  onUpdate === null || onUpdate === void 0 || onUpdate({
151
154
  textLength: text.length,
@@ -161,7 +164,8 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
161
164
  storedContextVector = null;
162
165
  storedLmLogits = null;
163
166
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
164
- errorType: 'network'
167
+ errorType: 'network',
168
+ isLocalLLM: false
165
169
  });
166
170
  if (isAutocompleteDebugEnabled()) {
167
171
  // eslint-disable-next-line no-console
@@ -191,7 +195,9 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
191
195
  debounceTimer = setTimeout(function () {
192
196
  debounceTimer = null;
193
197
  if (inflightRequestId !== null) {
194
- abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded');
198
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded', {
199
+ isLocalLLM: false
200
+ });
195
201
  }
196
202
  var requestId = String(++requestSeq);
197
203
  inflightRequestId = requestId;
@@ -35,4 +35,4 @@ export type AutocompleteExperienceName = (typeof EXPERIENCE_NAME)[keyof typeof E
35
35
  export declare const startExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
36
36
  export declare const succeedExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
37
37
  export declare const failExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
38
- export declare const abortExp: (name: AutocompleteExperienceName, id: string, reason?: string) => void;
38
+ export declare const abortExp: (name: AutocompleteExperienceName, id: string, reason?: string, metadata?: CustomData) => void;
@@ -40,6 +40,11 @@ export interface AutocompletePluginOptions {
40
40
  * Use this to serve vectors from a CDN or media service in production.
41
41
  */
42
42
  getVectorsBinaryUrl?: () => Promise<string>;
43
+ /**
44
+ * User locale used to determine whether autocomplete should run.
45
+ * Defaults to browser locale when omitted.
46
+ */
47
+ locale?: string;
43
48
  /**
44
49
  * When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
45
50
  * the network-based slow-lane backend. Defaults to false (network client).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-autocomplete",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  "wink-nlp": "^2.4.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "@atlaskit/editor-common": "^116.4.0",
30
+ "@atlaskit/editor-common": "^116.6.0",
31
31
  "@atlaskit/editor-plugin-analytics": "^12.0.0",
32
32
  "react": "^18.2.0"
33
33
  },
@@ -120,12 +120,23 @@ export const failExp = (
120
120
  }
121
121
  };
122
122
 
123
- export const abortExp = (name: AutocompleteExperienceName, id: string, reason?: string): void => {
123
+ export const abortExp = (
124
+ name: AutocompleteExperienceName,
125
+ id: string,
126
+ reason?: string,
127
+ metadata?: CustomData,
128
+ ): void => {
124
129
  if (!isUfoEnabled()) {
125
130
  return;
126
131
  }
127
132
  try {
128
- experiences[name].getInstance(id).abort(reason ? { metadata: { reason } } : undefined);
133
+ // `reason` is the dedicated, canonical channel for the abort reason and
134
+ // intentionally takes precedence over a `reason` key in `metadata`. Callers
135
+ // must pass the reason via the param, not inside `metadata` (the `reason`
136
+ // key there is reserved and would be overwritten).
137
+ const merged: CustomData = { ...metadata, ...(reason ? { reason } : {}) };
138
+ const hasMetadata = Object.keys(merged).length > 0;
139
+ experiences[name].getInstance(id).abort(hasMetadata ? { metadata: merged } : undefined);
129
140
  } catch {
130
141
  // UFO errors must never break the plugin
131
142
  }
@@ -214,6 +214,11 @@ export interface AutocompletePluginOptions {
214
214
  * Use this to serve vectors from a CDN or media service in production.
215
215
  */
216
216
  getVectorsBinaryUrl?: () => Promise<string>;
217
+ /**
218
+ * User locale used to determine whether autocomplete should run.
219
+ * Defaults to browser locale when omitted.
220
+ */
221
+ locale?: string;
217
222
  /**
218
223
  * When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
219
224
  * the network-based slow-lane backend. Defaults to false (network client).
@@ -299,10 +304,22 @@ const getTypedLengthForPrediction = (textBefore: string): number => {
299
304
  return trailingToken.length;
300
305
  };
301
306
 
307
+ const isEnglishLocale = (locale?: string): boolean => {
308
+ if (!locale) {
309
+ return false;
310
+ }
311
+
312
+ return locale.toLowerCase().startsWith('en');
313
+ };
314
+
302
315
  export const createAutocompletePlugin = (
303
316
  options?: AutocompletePluginOptions,
304
317
  api?: ExtractInjectionAPI<AutocompletePlugin>,
305
318
  ): SafePlugin<AutocompletePluginState> => {
319
+ const locale =
320
+ options?.locale ?? (typeof navigator !== 'undefined' ? navigator.language : undefined);
321
+ const isAutocompleteEnabled = isEnglishLocale(locale);
322
+
306
323
  let debounceTimer: ReturnType<typeof setTimeout> | null = null;
307
324
  let hasIngestedPage = false;
308
325
  let resolvedContext: AutocompleteContext | undefined;
@@ -730,6 +747,10 @@ export const createAutocompletePlugin = (
730
747
 
731
748
  handleDOMEvents: {
732
749
  blur: (view: EditorView) => {
750
+ if (!isAutocompleteEnabled) {
751
+ return false;
752
+ }
753
+
733
754
  const pluginState = autocompletePluginKey.getState(view.state) as
734
755
  | AutocompletePluginState
735
756
  | undefined;
@@ -740,6 +761,10 @@ export const createAutocompletePlugin = (
740
761
  return false;
741
762
  },
742
763
  focus: () => {
764
+ if (!isAutocompleteEnabled) {
765
+ return false;
766
+ }
767
+
743
768
  loadDefaultVocabulary({ isLocalLLM: options?.useLocalModel ?? false }).catch((error) => {
744
769
  logException(error as Error, {
745
770
  location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
@@ -767,6 +792,10 @@ export const createAutocompletePlugin = (
767
792
 
768
793
  view: () => ({
769
794
  update: (view: EditorView, prevState: EditorState) => {
795
+ if (!isAutocompleteEnabled) {
796
+ return;
797
+ }
798
+
770
799
  currentView = view;
771
800
  if (!prevState.doc.eq(view.state.doc)) {
772
801
  if (justAccepted) {
@@ -37,6 +37,8 @@
37
37
 
38
38
  import type { MLCEngine, InitProgressReport, AppConfig, LogitProcessor } from '@mlc-ai/web-llm';
39
39
 
40
+ import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
41
+
40
42
  import { isAutocompleteDebugEnabled } from './debug-mode';
41
43
  import { isWordBoundary } from './slow-lane-client';
42
44
 
@@ -987,6 +989,8 @@ export const createLocalSlowLaneClient = (
987
989
  return;
988
990
  }
989
991
 
992
+ const experienceId = String(requestId);
993
+
990
994
  // Clear the capture buffer so we read only this pass's logits. The engine
991
995
  // serialises per-model requests and updateContext is debounced, so the
992
996
  // latest request's decode step is the last to populate `captured` before
@@ -1025,6 +1029,17 @@ export const createLocalSlowLaneClient = (
1025
1029
  }
1026
1030
 
1027
1031
  try {
1032
+ // Reuse the network slow-lane-fetch UFO experience (tagged isLocalLLM:true,
1033
+ // matching LOAD_VECTORS/LOAD_VOCABULARY) so on-device inference
1034
+ // latency/success-rate feeds the same FE Reliability SLO. Started inside
1035
+ // the try — post engine-init (parity with the network fetch, which
1036
+ // excludes the one-time model load) and so the catch below always
1037
+ // terminates the experience, even on a synchronous throw.
1038
+ startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1039
+ textLength: text.length,
1040
+ isLocalLLM: true,
1041
+ });
1042
+
1028
1043
  const tStart = performance.now();
1029
1044
  let tLmDone = 0;
1030
1045
  let tEmbDone = 0;
@@ -1064,6 +1079,12 @@ export const createLocalSlowLaneClient = (
1064
1079
 
1065
1080
  // Discard stale results
1066
1081
  if (requestId < latestRequestId || destroyed) {
1082
+ abortExp(
1083
+ EXPERIENCE_NAME.SLOW_LANE_FETCH,
1084
+ experienceId,
1085
+ destroyed ? 'destroyed' : 'superseded',
1086
+ { isLocalLLM: true },
1087
+ );
1067
1088
  return;
1068
1089
  }
1069
1090
 
@@ -1126,6 +1147,13 @@ export const createLocalSlowLaneClient = (
1126
1147
  console.groupEnd();
1127
1148
  }
1128
1149
 
1150
+ succeedExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1151
+ textLength: text.length,
1152
+ hasVector: storedContextVector !== null,
1153
+ hasLmLogits: storedLmLogits !== null,
1154
+ isLocalLLM: true,
1155
+ });
1156
+
1129
1157
  onUpdate?.({
1130
1158
  textLength: text.length,
1131
1159
  hasVector: storedContextVector !== null,
@@ -1134,11 +1162,21 @@ export const createLocalSlowLaneClient = (
1134
1162
  } catch (err) {
1135
1163
  // Discard errors for stale requests or after teardown
1136
1164
  if (requestId < latestRequestId || destroyed) {
1165
+ abortExp(
1166
+ EXPERIENCE_NAME.SLOW_LANE_FETCH,
1167
+ experienceId,
1168
+ destroyed ? 'destroyed' : 'superseded',
1169
+ { isLocalLLM: true },
1170
+ );
1137
1171
  return;
1138
1172
  }
1139
1173
 
1140
1174
  storedContextVector = null;
1141
1175
  storedLmLogits = null;
1176
+ failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, experienceId, {
1177
+ errorType: 'inference',
1178
+ isLocalLLM: true,
1179
+ });
1142
1180
  onUpdate?.({ textLength: text.length, hasVector: false, hasLmLogits: false });
1143
1181
 
1144
1182
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -101,7 +101,10 @@ export const createSlowLaneClient = (
101
101
  session_id: sessionId,
102
102
  };
103
103
 
104
- startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, { textLength: text.length });
104
+ startExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
105
+ textLength: text.length,
106
+ isLocalLLM: false,
107
+ });
105
108
 
106
109
  if (isAutocompleteDebugEnabled()) {
107
110
  // eslint-disable-next-line no-console
@@ -131,6 +134,7 @@ export const createSlowLaneClient = (
131
134
  failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
132
135
  status: res.status,
133
136
  errorType: 'http_error',
137
+ isLocalLLM: false,
134
138
  });
135
139
  if (isAutocompleteDebugEnabled()) {
136
140
  // eslint-disable-next-line no-console
@@ -184,6 +188,7 @@ export const createSlowLaneClient = (
184
188
  textLength: text.length,
185
189
  hasVector: storedContextVector !== null,
186
190
  hasLmLogits: storedLmLogits !== null,
191
+ isLocalLLM: false,
187
192
  });
188
193
 
189
194
  onUpdate?.({
@@ -195,7 +200,10 @@ export const createSlowLaneClient = (
195
200
  } catch (e) {
196
201
  storedContextVector = null;
197
202
  storedLmLogits = null;
198
- failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, { errorType: 'network' });
203
+ failExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
204
+ errorType: 'network',
205
+ isLocalLLM: false,
206
+ });
199
207
  if (isAutocompleteDebugEnabled()) {
200
208
  // eslint-disable-next-line no-console
201
209
  console.log(
@@ -219,7 +227,9 @@ export const createSlowLaneClient = (
219
227
  debounceTimer = setTimeout(() => {
220
228
  debounceTimer = null;
221
229
  if (inflightRequestId !== null) {
222
- abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded');
230
+ abortExp(EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded', {
231
+ isLocalLLM: false,
232
+ });
223
233
  }
224
234
  const requestId = String(++requestSeq);
225
235
  inflightRequestId = requestId;