@atlaskit/editor-plugin-autocomplete 2.4.0 → 2.5.1

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 (31) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/afm-products/tsconfig.json +3 -0
  4. package/dist/cjs/analytics/ufo.js +111 -0
  5. package/dist/cjs/pm-plugins/autocomplete-plugin.js +126 -64
  6. package/dist/cjs/pm-plugins/local-slow-lane-client.js +452 -0
  7. package/dist/cjs/pm-plugins/slow-lane-client.js +45 -16
  8. package/dist/cjs/pm-plugins/text-predictor.js +72 -40
  9. package/dist/es2019/analytics/ufo.js +110 -0
  10. package/dist/es2019/pm-plugins/autocomplete-plugin.js +132 -72
  11. package/dist/es2019/pm-plugins/local-slow-lane-client.js +361 -0
  12. package/dist/es2019/pm-plugins/slow-lane-client.js +29 -2
  13. package/dist/es2019/pm-plugins/text-predictor.js +48 -15
  14. package/dist/esm/analytics/ufo.js +105 -0
  15. package/dist/esm/pm-plugins/autocomplete-plugin.js +126 -64
  16. package/dist/esm/pm-plugins/local-slow-lane-client.js +443 -0
  17. package/dist/esm/pm-plugins/slow-lane-client.js +45 -16
  18. package/dist/esm/pm-plugins/text-predictor.js +73 -40
  19. package/dist/types/analytics/ufo.d.ts +38 -0
  20. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +5 -0
  21. package/dist/types/pm-plugins/local-slow-lane-client.d.ts +102 -0
  22. package/dist/types-ts4.5/analytics/ufo.d.ts +38 -0
  23. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +5 -0
  24. package/dist/types-ts4.5/pm-plugins/local-slow-lane-client.d.ts +102 -0
  25. package/package.json +7 -2
  26. package/src/analytics/ufo.ts +132 -0
  27. package/src/pm-plugins/autocomplete-plugin.ts +125 -64
  28. package/src/pm-plugins/local-slow-lane-client.ts +487 -0
  29. package/src/pm-plugins/slow-lane-client.ts +28 -2
  30. package/src/pm-plugins/text-predictor.ts +42 -12
  31. package/tsconfig.app.json +3 -0
@@ -0,0 +1,452 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.createLocalSlowLaneClient = exports.LOCAL_MLC_MODEL_LIB_WASM_NAME = exports.LOCAL_MLC_MODEL_ID = exports.LOCAL_MLC_HF_MODEL_REPO = exports.HUGGINGFACE_TB_SMOLLM_ADD_BASICS_REPO = void 0;
9
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
14
+ var _debugMode = require("./debug-mode");
15
+ var _slowLaneClient = require("./slow-lane-client");
16
+ 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; } } }; }
17
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
18
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
19
+ 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; }
20
+ 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; }
21
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } /**
22
+ * Local Slow Lane Client: On-device inference via @mlc-ai/web-llm.
23
+ *
24
+ * Drop-in replacement for the network-based slow-lane-client. Instead of
25
+ * calling a backend API, this client uses MLC WebLLM to run a small language
26
+ * model (SmolLM 135M) directly in the browser via WebGPU.
27
+ *
28
+ * ── Why main thread (no Web Worker)? ─────────────────────────────────────
29
+ * SmolLM 135M is small enough (~270 MB weights, 350-400 MB VRAM) that
30
+ * WebGPU inference on the main thread is production-viable:
31
+ *
32
+ * - WebGPU GPU compute is inherently async (doesn't block the main thread)
33
+ * - CPU overhead (tokenization + post-processing) is only 5-10 ms
34
+ * - Single forward pass latency is 50-150 ms — well within autocomplete
35
+ * expectations (~250 ms between word boundaries)
36
+ *
37
+ * This avoids all the complexity of Web Workers:
38
+ * - No CSP workarounds (blob URLs, inline scripts)
39
+ * - No bundler configuration (worker-plugin, import.meta.url)
40
+ * - No message passing protocol
41
+ * - Standard npm import — just works
42
+ *
43
+ * ── Interface ────────────────────────────────────────────────────────────
44
+ * Same shape as createSlowLaneClient so text-predictor.ts needs zero changes.
45
+ * The client exposes getContextVector() and getLmLogits() which are populated
46
+ * asynchronously after each updateContext() call.
47
+ */
48
+ var startsWithAsciiLetter = function startsWithAsciiLetter(value) {
49
+ var firstChar = value.charCodeAt(0);
50
+ return firstChar >= 65 && firstChar <= 90 || firstChar >= 97 && firstChar <= 122;
51
+ };
52
+
53
+ // ─── Types ───────────────────────────────────────────────────────────────────
54
+
55
+ // Same return type as createSlowLaneClient for drop-in compatibility
56
+
57
+ // ─── Constants ───────────────────────────────────────────────────────────────
58
+
59
+ var DEFAULT_DEBOUNCE_MS = 300;
60
+ var LOCAL_MLC_MODEL_ID = exports.LOCAL_MLC_MODEL_ID = 'SmolLM2-135M-Instruct-q0f16-MLC';
61
+
62
+ /** HF root for the default weights (includes `tensor-cache.json` for WebLLM 0.2+). */
63
+ var LOCAL_MLC_HF_MODEL_REPO = exports.LOCAL_MLC_HF_MODEL_REPO = 'https://huggingface.co/mlc-ai/SmolLM2-135M-Instruct-q0f16-MLC';
64
+ var LOCAL_MLC_MODEL_LIB_WASM_NAME = exports.LOCAL_MLC_MODEL_LIB_WASM_NAME = 'SmolLM2-135M-Instruct-q0f16-ctx4k_cs1k-webgpu.wasm';
65
+
66
+ /**
67
+ * Original target repo (add-basics fine-tune). **Not compatible with WebLLM 0.2.x** (no `tensor-cache.json`).
68
+ * @see module doc above
69
+ */
70
+ var HUGGINGFACE_TB_SMOLLM_ADD_BASICS_REPO = exports.HUGGINGFACE_TB_SMOLLM_ADD_BASICS_REPO = 'https://huggingface.co/HuggingFaceTB/smollm-135M-instruct-add-basics-q0f16-MLC';
71
+
72
+ // ─── Factory ─────────────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Create a local slow-lane client powered by MLC WebLLM.
76
+ *
77
+ * The engine is initialised lazily — model weights are downloaded (and cached
78
+ * in IndexedDB) on first use. Subsequent page loads skip the download.
79
+ *
80
+ * Usage:
81
+ * ```ts
82
+ * const client = createLocalSlowLaneClient({ debounceMs: 300 });
83
+ * // On word boundaries:
84
+ * client.updateContext(docText);
85
+ * // In scoring pipeline:
86
+ * const vec = client.getContextVector();
87
+ * const logits = client.getLmLogits();
88
+ * // On plugin teardown:
89
+ * client.destroy();
90
+ * ```
91
+ */
92
+ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function createLocalSlowLaneClient() {
93
+ var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
94
+ var _config$debounceMs = config.debounceMs,
95
+ debounceMs = _config$debounceMs === void 0 ? DEFAULT_DEBOUNCE_MS : _config$debounceMs,
96
+ onUpdate = config.onUpdate,
97
+ onStatus = config.onStatus,
98
+ _config$modelId = config.modelId,
99
+ modelId = _config$modelId === void 0 ? LOCAL_MLC_MODEL_ID : _config$modelId,
100
+ customModelConfig = config.customModelConfig;
101
+
102
+ // ── State ──────────────────────────────────────────────────────────────
103
+ var storedContextVector = null;
104
+ var storedLmLogits = null;
105
+ var debounceTimer = null;
106
+ var lastRequestedText = '';
107
+ var requestCounter = 0;
108
+ var latestRequestId = -1;
109
+ var ready = false;
110
+ var destroyed = false;
111
+ var initFailed = false;
112
+ var engine = null;
113
+ var engineInitPromise = null;
114
+ var unloadEngine = function unloadEngine(engineToUnload) {
115
+ engineToUnload.unload().catch(function (error) {
116
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
117
+ // eslint-disable-next-line no-console
118
+ console.log('%c[LocalSlowLane] %cFailed to unload engine', 'color: #9c27b0; font-weight: bold;', 'color: inherit;', error);
119
+ }
120
+ });
121
+ };
122
+
123
+ // ── Engine initialisation ──────────────────────────────────────────────
124
+
125
+ var initProgressCallback = function initProgressCallback(progress) {
126
+ var message = "[".concat((progress.progress * 100).toFixed(0), "%] ").concat(progress.text);
127
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
128
+ // eslint-disable-next-line no-console
129
+ console.log("%c[LocalSlowLane] %c\uD83D\uDD04 ".concat(message), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
130
+ }
131
+ onStatus === null || onStatus === void 0 || onStatus(message);
132
+ };
133
+ var initEngine = /*#__PURE__*/function () {
134
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
135
+ var _yield$import, CreateMLCEngine, prebuiltAppConfig, customModelRecord, appConfig, errorMsg;
136
+ return _regenerator.default.wrap(function _callee$(_context) {
137
+ while (1) switch (_context.prev = _context.next) {
138
+ case 0:
139
+ _context.prev = 0;
140
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
141
+ // eslint-disable-next-line no-console
142
+ console.log("%c[LocalSlowLane] %c\uD83D\uDE80 Initialising MLC engine with model: ".concat(modelId), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
143
+ }
144
+ onStatus === null || onStatus === void 0 || onStatus("Initialising model: ".concat(modelId, "\u2026"));
145
+ if ('gpu' in navigator) {
146
+ _context.next = 5;
147
+ break;
148
+ }
149
+ throw new Error('WebGPU not supported');
150
+ case 5:
151
+ _context.next = 7;
152
+ return Promise.resolve().then(function () {
153
+ return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-mlc-web-llm" */'@mlc-ai/web-llm'));
154
+ });
155
+ case 7:
156
+ _yield$import = _context.sent;
157
+ CreateMLCEngine = _yield$import.CreateMLCEngine;
158
+ prebuiltAppConfig = _yield$import.prebuiltAppConfig;
159
+ customModelRecord = customModelConfig ? _objectSpread(_objectSpread({
160
+ model: customModelConfig.model,
161
+ model_id: modelId,
162
+ model_lib: customModelConfig.modelLib,
163
+ low_resource_required: true,
164
+ required_features: ['shader-f16']
165
+ }, customModelConfig.vramRequiredMB !== undefined ? {
166
+ vram_required_MB: customModelConfig.vramRequiredMB
167
+ } : {}), customModelConfig.contextWindowSize !== undefined ? {
168
+ overrides: {
169
+ context_window_size: customModelConfig.contextWindowSize
170
+ }
171
+ } : {}) : undefined;
172
+ appConfig = {
173
+ model_list: [].concat((0, _toConsumableArray2.default)(prebuiltAppConfig.model_list), (0, _toConsumableArray2.default)(customModelRecord ? [customModelRecord] : []))
174
+ };
175
+ _context.next = 14;
176
+ return CreateMLCEngine(modelId, {
177
+ appConfig: appConfig,
178
+ initProgressCallback: initProgressCallback
179
+ });
180
+ case 14:
181
+ engine = _context.sent;
182
+ if (!destroyed) {
183
+ _context.next = 19;
184
+ break;
185
+ }
186
+ // destroy() was called while we were loading — clean up
187
+ unloadEngine(engine);
188
+ engine = null;
189
+ return _context.abrupt("return");
190
+ case 19:
191
+ ready = true;
192
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
193
+ // eslint-disable-next-line no-console
194
+ console.log('%c[LocalSlowLane] %c✅ MLC engine loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
195
+ }
196
+ onStatus === null || onStatus === void 0 || onStatus('Model loaded and ready.');
197
+ _context.next = 32;
198
+ break;
199
+ case 24:
200
+ _context.prev = 24;
201
+ _context.t0 = _context["catch"](0);
202
+ errorMsg = _context.t0 instanceof Error ? _context.t0.message : String(_context.t0);
203
+ ready = false;
204
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
205
+ // eslint-disable-next-line no-console
206
+ console.log("[LocalSlowLane] Engine initialisation failed: ".concat(errorMsg));
207
+ }
208
+ onStatus === null || onStatus === void 0 || onStatus("Engine initialisation failed: ".concat(errorMsg));
209
+ engineInitPromise = null;
210
+ initFailed = true;
211
+ case 32:
212
+ case "end":
213
+ return _context.stop();
214
+ }
215
+ }, _callee, null, [[0, 24]]);
216
+ }));
217
+ return function initEngine() {
218
+ return _ref.apply(this, arguments);
219
+ };
220
+ }();
221
+ var ensureEngineInitialized = function ensureEngineInitialized() {
222
+ if (initFailed) {
223
+ return Promise.resolve();
224
+ }
225
+ if (!engineInitPromise) {
226
+ engineInitPromise = initEngine();
227
+ }
228
+ return engineInitPromise;
229
+ };
230
+
231
+ // ── Inference ──────────────────────────────────────────────────────────
232
+
233
+ /**
234
+ * Run a single forward pass to extract next-token logit probabilities.
235
+ *
236
+ * We use the chat completions API with `max_tokens: 1` and `logprobs: true`
237
+ * to get the model's next-token distribution without generating text.
238
+ * This is the cheapest possible inference call — a single forward pass.
239
+ */
240
+ var runInference = /*#__PURE__*/function () {
241
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(text, requestId) {
242
+ var _response$choices, response, lmLogits, logprobsContent, tokenLogprobs, token, _iterator, _step, alt, _token, logitValues, topTokens, errorMsg;
243
+ return _regenerator.default.wrap(function _callee2$(_context2) {
244
+ while (1) switch (_context2.prev = _context2.next) {
245
+ case 0:
246
+ if (!(!engine || destroyed)) {
247
+ _context2.next = 2;
248
+ break;
249
+ }
250
+ return _context2.abrupt("return");
251
+ case 2:
252
+ _context2.prev = 2;
253
+ _context2.next = 5;
254
+ return engine.chat.completions.create({
255
+ messages: [{
256
+ role: 'user',
257
+ content: text
258
+ }],
259
+ max_tokens: 1,
260
+ logprobs: true,
261
+ top_logprobs: 5,
262
+ temperature: 0
263
+ });
264
+ case 5:
265
+ response = _context2.sent;
266
+ if (!(requestId < latestRequestId || destroyed)) {
267
+ _context2.next = 8;
268
+ break;
269
+ }
270
+ return _context2.abrupt("return");
271
+ case 8:
272
+ // ── Extract LM logits ───────────────────────────────────────
273
+ lmLogits = {};
274
+ logprobsContent = (_response$choices = response.choices) === null || _response$choices === void 0 || (_response$choices = _response$choices[0]) === null || _response$choices === void 0 || (_response$choices = _response$choices.logprobs) === null || _response$choices === void 0 ? void 0 : _response$choices.content;
275
+ if (logprobsContent && logprobsContent.length > 0) {
276
+ tokenLogprobs = logprobsContent[0]; // Add the top token
277
+ if (tokenLogprobs.token) {
278
+ token = tokenLogprobs.token.trim().toLowerCase();
279
+ if (token.length > 0 && startsWithAsciiLetter(token)) {
280
+ lmLogits[token] = Math.exp(tokenLogprobs.logprob);
281
+ }
282
+ }
283
+
284
+ // Add alternative tokens from top_logprobs
285
+ if (tokenLogprobs.top_logprobs) {
286
+ _iterator = _createForOfIteratorHelper(tokenLogprobs.top_logprobs);
287
+ try {
288
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
289
+ alt = _step.value;
290
+ _token = alt.token.trim().toLowerCase();
291
+ if (_token.length > 0 && startsWithAsciiLetter(_token)) {
292
+ lmLogits[_token] = Math.exp(alt.logprob);
293
+ }
294
+ }
295
+ } catch (err) {
296
+ _iterator.e(err);
297
+ } finally {
298
+ _iterator.f();
299
+ }
300
+ }
301
+ }
302
+ storedLmLogits = Object.keys(lmLogits).length > 0 ? lmLogits : null;
303
+
304
+ // ── Semantic vector ─────────────────────────────────────────
305
+ // SmolLM is a generative model, not an embedding model, so we
306
+ // don't get a true semantic vector. We generate a lightweight
307
+ // pseudo-embedding from the logit distribution for compatibility
308
+ // with the existing scoring pipeline.
309
+ //
310
+ // For a production implementation, you would use a dedicated
311
+ // embedding model (e.g. via web-llm's embeddings API with an
312
+ // embedding-specific model).
313
+ if (storedLmLogits) {
314
+ logitValues = Object.values(storedLmLogits);
315
+ storedContextVector = new Float32Array(logitValues);
316
+ } else {
317
+ storedContextVector = null;
318
+ }
319
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
320
+ // eslint-disable-next-line no-console
321
+ console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE5 Inference result (request #".concat(requestId, ")"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
322
+ // eslint-disable-next-line no-console
323
+ console.log(storedContextVector ? "\u2705 pseudo-vector: ".concat(storedContextVector.length, " dims") : '❌ No vector');
324
+ // eslint-disable-next-line no-console
325
+ console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
326
+ if (storedLmLogits) {
327
+ topTokens = Object.entries(storedLmLogits).sort(function (_ref3, _ref4) {
328
+ var _ref5 = (0, _slicedToArray2.default)(_ref3, 2),
329
+ a = _ref5[1];
330
+ var _ref6 = (0, _slicedToArray2.default)(_ref4, 2),
331
+ b = _ref6[1];
332
+ return b - a;
333
+ }).slice(0, 10); // eslint-disable-next-line no-console
334
+ console.log('Top 10 predictions:', topTokens.map(function (_ref7) {
335
+ var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
336
+ t = _ref8[0],
337
+ p = _ref8[1];
338
+ return "".concat(t, ": ").concat((p * 100).toFixed(1), "%");
339
+ }).join(', '));
340
+ }
341
+ // eslint-disable-next-line no-console
342
+ console.groupEnd();
343
+ }
344
+ onUpdate === null || onUpdate === void 0 || onUpdate({
345
+ textLength: text.length,
346
+ hasVector: storedContextVector !== null,
347
+ hasLmLogits: storedLmLogits !== null
348
+ });
349
+ _context2.next = 26;
350
+ break;
351
+ case 17:
352
+ _context2.prev = 17;
353
+ _context2.t0 = _context2["catch"](2);
354
+ if (!(requestId < latestRequestId)) {
355
+ _context2.next = 21;
356
+ break;
357
+ }
358
+ return _context2.abrupt("return");
359
+ case 21:
360
+ storedContextVector = null;
361
+ storedLmLogits = null;
362
+ onUpdate === null || onUpdate === void 0 || onUpdate({
363
+ textLength: text.length,
364
+ hasVector: false,
365
+ hasLmLogits: false
366
+ });
367
+ errorMsg = _context2.t0 instanceof Error ? _context2.t0.message : String(_context2.t0);
368
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
369
+ // eslint-disable-next-line no-console
370
+ console.log("%c[LocalSlowLane] %c\u274C Inference error (request #".concat(requestId, "): ").concat(errorMsg), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
371
+ }
372
+ case 26:
373
+ case "end":
374
+ return _context2.stop();
375
+ }
376
+ }, _callee2, null, [[2, 17]]);
377
+ }));
378
+ return function runInference(_x, _x2) {
379
+ return _ref2.apply(this, arguments);
380
+ };
381
+ }();
382
+
383
+ // ── Context update (debounced) ─────────────────────────────────────────
384
+
385
+ var doUpdateContext = function doUpdateContext(text) {
386
+ if (destroyed || !text || text.trim().length === 0) {
387
+ return;
388
+ }
389
+ var requestId = ++requestCounter;
390
+ latestRequestId = requestId;
391
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
392
+ // eslint-disable-next-line no-console
393
+ console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE4 Context update (request #".concat(requestId, ") | ").concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
394
+ var lines = text.split('\n');
395
+ lines.forEach(function (line, i) {
396
+ // eslint-disable-next-line no-console
397
+ console.log(" ".concat(i === lines.length - 1 ? '▶' : ' ', " ").concat(line));
398
+ });
399
+ // eslint-disable-next-line no-console
400
+ console.groupEnd();
401
+ }
402
+ void ensureEngineInitialized().then(function () {
403
+ return runInference(text, requestId);
404
+ }).catch(function () {});
405
+ };
406
+ var updateContextDebounced = function updateContextDebounced(text) {
407
+ if (debounceTimer) {
408
+ clearTimeout(debounceTimer);
409
+ }
410
+ lastRequestedText = text;
411
+ debounceTimer = setTimeout(function () {
412
+ debounceTimer = null;
413
+ doUpdateContext(lastRequestedText);
414
+ }, debounceMs);
415
+ };
416
+
417
+ // ── Public API (same shape as createSlowLaneClient) ────────────────────
418
+ return {
419
+ updateContext: updateContextDebounced,
420
+ getContextVector: function getContextVector() {
421
+ return storedContextVector;
422
+ },
423
+ getLmLogits: function getLmLogits() {
424
+ return storedLmLogits;
425
+ },
426
+ setContextVector: function setContextVector(vector) {
427
+ storedContextVector = vector;
428
+ },
429
+ setLmLogits: function setLmLogits(logits) {
430
+ storedLmLogits = logits;
431
+ },
432
+ isWordBoundary: _slowLaneClient.isWordBoundary,
433
+ isReady: function isReady() {
434
+ return ready;
435
+ },
436
+ destroy: function destroy() {
437
+ destroyed = true;
438
+ ready = false;
439
+ if (debounceTimer) {
440
+ clearTimeout(debounceTimer);
441
+ }
442
+ if (engine) {
443
+ var engineToUnload = engine;
444
+ engine = null;
445
+ unloadEngine(engineToUnload);
446
+ }
447
+ engineInitPromise = null;
448
+ storedContextVector = null;
449
+ storedLmLogits = null;
450
+ }
451
+ };
452
+ };
@@ -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 _ufo = require("../analytics/ufo");
11
12
  var _debugMode = require("./debug-mode");
12
13
  /**
13
14
  * Slow Lane Client: Backend context encoding for autocomplete.
@@ -65,8 +66,10 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
65
66
  var lastRequestedText = '';
66
67
  var storedContextVector = null;
67
68
  var storedLmLogits = null;
69
+ var requestSeq = 0;
70
+ var inflightRequestId = null;
68
71
  var doUpdateContext = /*#__PURE__*/function () {
69
- var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(text) {
72
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(text, requestId) {
70
73
  var url, payload, res, data;
71
74
  return _regenerator.default.wrap(function _callee$(_context) {
72
75
  while (1) switch (_context.prev = _context.next) {
@@ -83,6 +86,9 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
83
86
  text: text,
84
87
  session_id: sessionId
85
88
  };
89
+ (0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
90
+ textLength: text.length
91
+ });
86
92
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
87
93
  // eslint-disable-next-line no-console
88
94
  console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE4 Sending context | ".concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
@@ -93,30 +99,34 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
93
99
  // eslint-disable-next-line no-console
94
100
  console.groupEnd();
95
101
  }
96
- _context.prev = 5;
97
- _context.next = 8;
102
+ _context.prev = 6;
103
+ _context.next = 9;
98
104
  return fetchFn(url, {
99
105
  method: 'POST',
100
106
  headers: headers,
101
107
  body: JSON.stringify(payload)
102
108
  });
103
- case 8:
109
+ case 9:
104
110
  res = _context.sent;
105
111
  if (res.ok) {
106
- _context.next = 14;
112
+ _context.next = 16;
107
113
  break;
108
114
  }
109
115
  storedContextVector = null;
110
116
  storedLmLogits = null;
117
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
118
+ status: res.status,
119
+ errorType: 'http_error'
120
+ });
111
121
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
112
122
  // eslint-disable-next-line no-console
113
123
  console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
114
124
  }
115
125
  return _context.abrupt("return");
116
- case 14:
117
- _context.next = 16;
118
- return res.json();
119
126
  case 16:
127
+ _context.next = 18;
128
+ return res.json();
129
+ case 18:
120
130
  data = _context.sent;
121
131
  if (data.semantic_vector && Array.isArray(data.semantic_vector)) {
122
132
  storedContextVector = new Float32Array(data.semantic_vector);
@@ -138,30 +148,44 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
138
148
  // eslint-disable-next-line no-console
139
149
  console.groupEnd();
140
150
  }
151
+ (0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
152
+ textLength: text.length,
153
+ hasVector: storedContextVector !== null,
154
+ hasLmLogits: storedLmLogits !== null
155
+ });
141
156
  onUpdate === null || onUpdate === void 0 || onUpdate({
142
157
  textLength: text.length,
143
158
  hasVector: storedContextVector !== null,
144
159
  hasLmLogits: storedLmLogits !== null
145
160
  });
146
161
  // eslint-disable-next-line no-unused-vars
147
- _context.next = 28;
162
+ _context.next = 32;
148
163
  break;
149
- case 23:
150
- _context.prev = 23;
151
- _context.t0 = _context["catch"](5);
164
+ case 26:
165
+ _context.prev = 26;
166
+ _context.t0 = _context["catch"](6);
152
167
  storedContextVector = null;
153
168
  storedLmLogits = null;
169
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, requestId, {
170
+ errorType: 'network'
171
+ });
154
172
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
155
173
  // eslint-disable-next-line no-console
156
174
  console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
157
175
  }
158
- case 28:
176
+ case 32:
177
+ _context.prev = 32;
178
+ if (inflightRequestId === requestId) {
179
+ inflightRequestId = null;
180
+ }
181
+ return _context.finish(32);
182
+ case 35:
159
183
  case "end":
160
184
  return _context.stop();
161
185
  }
162
- }, _callee, null, [[5, 23]]);
186
+ }, _callee, null, [[6, 26, 32, 35]]);
163
187
  }));
164
- return function doUpdateContext(_x) {
188
+ return function doUpdateContext(_x, _x2) {
165
189
  return _ref.apply(this, arguments);
166
190
  };
167
191
  }();
@@ -172,7 +196,12 @@ var createSlowLaneClient = exports.createSlowLaneClient = function createSlowLan
172
196
  lastRequestedText = text;
173
197
  debounceTimer = setTimeout(function () {
174
198
  debounceTimer = null;
175
- doUpdateContext(lastRequestedText);
199
+ if (inflightRequestId !== null) {
200
+ (0, _ufo.abortExp)(_ufo.EXPERIENCE_NAME.SLOW_LANE_FETCH, inflightRequestId, 'superseded');
201
+ }
202
+ var requestId = String(++requestSeq);
203
+ inflightRequestId = requestId;
204
+ doUpdateContext(lastRequestedText, requestId);
176
205
  }, debounceMs);
177
206
  };
178
207
  return {