@atlaskit/editor-plugin-autocomplete 2.4.0 → 2.5.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 (31) hide show
  1. package/CHANGELOG.md +31 -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 +447 -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 +355 -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 +439 -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 +139 -0
  27. package/src/pm-plugins/autocomplete-plugin.ts +126 -64
  28. package/src/pm-plugins/local-slow-lane-client.ts +480 -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,447 @@
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
+ // ─── Types ───────────────────────────────────────────────────────────────────
49
+
50
+ // Same return type as createSlowLaneClient for drop-in compatibility
51
+
52
+ // ─── Constants ───────────────────────────────────────────────────────────────
53
+
54
+ var DEFAULT_DEBOUNCE_MS = 300;
55
+ var LOCAL_MLC_MODEL_ID = exports.LOCAL_MLC_MODEL_ID = 'SmolLM2-135M-Instruct-q0f16-MLC';
56
+
57
+ /** HF root for the default weights (includes `tensor-cache.json` for WebLLM 0.2+). */
58
+ var LOCAL_MLC_HF_MODEL_REPO = exports.LOCAL_MLC_HF_MODEL_REPO = 'https://huggingface.co/mlc-ai/SmolLM2-135M-Instruct-q0f16-MLC';
59
+ var LOCAL_MLC_MODEL_LIB_WASM_NAME = exports.LOCAL_MLC_MODEL_LIB_WASM_NAME = 'SmolLM2-135M-Instruct-q0f16-ctx4k_cs1k-webgpu.wasm';
60
+
61
+ /**
62
+ * Original target repo (add-basics fine-tune). **Not compatible with WebLLM 0.2.x** (no `tensor-cache.json`).
63
+ * @see module doc above
64
+ */
65
+ 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';
66
+
67
+ // ─── Factory ─────────────────────────────────────────────────────────────────
68
+
69
+ /**
70
+ * Create a local slow-lane client powered by MLC WebLLM.
71
+ *
72
+ * The engine is initialised lazily — model weights are downloaded (and cached
73
+ * in IndexedDB) on first use. Subsequent page loads skip the download.
74
+ *
75
+ * Usage:
76
+ * ```ts
77
+ * const client = createLocalSlowLaneClient({ debounceMs: 300 });
78
+ * // On word boundaries:
79
+ * client.updateContext(docText);
80
+ * // In scoring pipeline:
81
+ * const vec = client.getContextVector();
82
+ * const logits = client.getLmLogits();
83
+ * // On plugin teardown:
84
+ * client.destroy();
85
+ * ```
86
+ */
87
+ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function createLocalSlowLaneClient() {
88
+ var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
89
+ var _config$debounceMs = config.debounceMs,
90
+ debounceMs = _config$debounceMs === void 0 ? DEFAULT_DEBOUNCE_MS : _config$debounceMs,
91
+ onUpdate = config.onUpdate,
92
+ onStatus = config.onStatus,
93
+ _config$modelId = config.modelId,
94
+ modelId = _config$modelId === void 0 ? LOCAL_MLC_MODEL_ID : _config$modelId,
95
+ customModelConfig = config.customModelConfig;
96
+
97
+ // ── State ──────────────────────────────────────────────────────────────
98
+ var storedContextVector = null;
99
+ var storedLmLogits = null;
100
+ var debounceTimer = null;
101
+ var lastRequestedText = '';
102
+ var requestCounter = 0;
103
+ var latestRequestId = -1;
104
+ var ready = false;
105
+ var destroyed = false;
106
+ var initFailed = false;
107
+ var engine = null;
108
+ var engineInitPromise = null;
109
+ var unloadEngine = function unloadEngine(engineToUnload) {
110
+ engineToUnload.unload().catch(function (error) {
111
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
112
+ // eslint-disable-next-line no-console
113
+ console.log('%c[LocalSlowLane] %cFailed to unload engine', 'color: #9c27b0; font-weight: bold;', 'color: inherit;', error);
114
+ }
115
+ });
116
+ };
117
+
118
+ // ── Engine initialisation ──────────────────────────────────────────────
119
+
120
+ var initProgressCallback = function initProgressCallback(progress) {
121
+ var message = "[".concat((progress.progress * 100).toFixed(0), "%] ").concat(progress.text);
122
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
123
+ // eslint-disable-next-line no-console
124
+ console.log("%c[LocalSlowLane] %c\uD83D\uDD04 ".concat(message), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
125
+ }
126
+ onStatus === null || onStatus === void 0 || onStatus(message);
127
+ };
128
+ var initEngine = /*#__PURE__*/function () {
129
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
130
+ var _yield$import, CreateMLCEngine, prebuiltAppConfig, customModelRecord, appConfig, errorMsg;
131
+ return _regenerator.default.wrap(function _callee$(_context) {
132
+ while (1) switch (_context.prev = _context.next) {
133
+ case 0:
134
+ _context.prev = 0;
135
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
136
+ // eslint-disable-next-line no-console
137
+ console.log("%c[LocalSlowLane] %c\uD83D\uDE80 Initialising MLC engine with model: ".concat(modelId), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
138
+ }
139
+ onStatus === null || onStatus === void 0 || onStatus("Initialising model: ".concat(modelId, "\u2026"));
140
+ if ('gpu' in navigator) {
141
+ _context.next = 5;
142
+ break;
143
+ }
144
+ throw new Error('WebGPU not supported');
145
+ case 5:
146
+ _context.next = 7;
147
+ return Promise.resolve().then(function () {
148
+ return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-mlc-web-llm" */'@mlc-ai/web-llm'));
149
+ });
150
+ case 7:
151
+ _yield$import = _context.sent;
152
+ CreateMLCEngine = _yield$import.CreateMLCEngine;
153
+ prebuiltAppConfig = _yield$import.prebuiltAppConfig;
154
+ customModelRecord = customModelConfig ? _objectSpread(_objectSpread({
155
+ model: customModelConfig.model,
156
+ model_id: modelId,
157
+ model_lib: customModelConfig.modelLib,
158
+ low_resource_required: true,
159
+ required_features: ['shader-f16']
160
+ }, customModelConfig.vramRequiredMB !== undefined ? {
161
+ vram_required_MB: customModelConfig.vramRequiredMB
162
+ } : {}), customModelConfig.contextWindowSize !== undefined ? {
163
+ overrides: {
164
+ context_window_size: customModelConfig.contextWindowSize
165
+ }
166
+ } : {}) : undefined;
167
+ appConfig = {
168
+ model_list: [].concat((0, _toConsumableArray2.default)(prebuiltAppConfig.model_list), (0, _toConsumableArray2.default)(customModelRecord ? [customModelRecord] : []))
169
+ };
170
+ _context.next = 14;
171
+ return CreateMLCEngine(modelId, {
172
+ appConfig: appConfig,
173
+ initProgressCallback: initProgressCallback
174
+ });
175
+ case 14:
176
+ engine = _context.sent;
177
+ if (!destroyed) {
178
+ _context.next = 19;
179
+ break;
180
+ }
181
+ // destroy() was called while we were loading — clean up
182
+ unloadEngine(engine);
183
+ engine = null;
184
+ return _context.abrupt("return");
185
+ case 19:
186
+ ready = true;
187
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
188
+ // eslint-disable-next-line no-console
189
+ console.log('%c[LocalSlowLane] %c✅ MLC engine loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
190
+ }
191
+ onStatus === null || onStatus === void 0 || onStatus('Model loaded and ready.');
192
+ _context.next = 32;
193
+ break;
194
+ case 24:
195
+ _context.prev = 24;
196
+ _context.t0 = _context["catch"](0);
197
+ errorMsg = _context.t0 instanceof Error ? _context.t0.message : String(_context.t0);
198
+ ready = false;
199
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
200
+ // eslint-disable-next-line no-console
201
+ console.log("[LocalSlowLane] Engine initialisation failed: ".concat(errorMsg));
202
+ }
203
+ onStatus === null || onStatus === void 0 || onStatus("Engine initialisation failed: ".concat(errorMsg));
204
+ engineInitPromise = null;
205
+ initFailed = true;
206
+ case 32:
207
+ case "end":
208
+ return _context.stop();
209
+ }
210
+ }, _callee, null, [[0, 24]]);
211
+ }));
212
+ return function initEngine() {
213
+ return _ref.apply(this, arguments);
214
+ };
215
+ }();
216
+ var ensureEngineInitialized = function ensureEngineInitialized() {
217
+ if (initFailed) {
218
+ return Promise.resolve();
219
+ }
220
+ if (!engineInitPromise) {
221
+ engineInitPromise = initEngine();
222
+ }
223
+ return engineInitPromise;
224
+ };
225
+
226
+ // ── Inference ──────────────────────────────────────────────────────────
227
+
228
+ /**
229
+ * Run a single forward pass to extract next-token logit probabilities.
230
+ *
231
+ * We use the chat completions API with `max_tokens: 1` and `logprobs: true`
232
+ * to get the model's next-token distribution without generating text.
233
+ * This is the cheapest possible inference call — a single forward pass.
234
+ */
235
+ var runInference = /*#__PURE__*/function () {
236
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(text, requestId) {
237
+ var _response$choices, response, lmLogits, logprobsContent, tokenLogprobs, token, _iterator, _step, alt, _token, logitValues, topTokens, errorMsg;
238
+ return _regenerator.default.wrap(function _callee2$(_context2) {
239
+ while (1) switch (_context2.prev = _context2.next) {
240
+ case 0:
241
+ if (!(!engine || destroyed)) {
242
+ _context2.next = 2;
243
+ break;
244
+ }
245
+ return _context2.abrupt("return");
246
+ case 2:
247
+ _context2.prev = 2;
248
+ _context2.next = 5;
249
+ return engine.chat.completions.create({
250
+ messages: [{
251
+ role: 'user',
252
+ content: text
253
+ }],
254
+ max_tokens: 1,
255
+ logprobs: true,
256
+ top_logprobs: 5,
257
+ temperature: 0
258
+ });
259
+ case 5:
260
+ response = _context2.sent;
261
+ if (!(requestId < latestRequestId || destroyed)) {
262
+ _context2.next = 8;
263
+ break;
264
+ }
265
+ return _context2.abrupt("return");
266
+ case 8:
267
+ // ── Extract LM logits ───────────────────────────────────────
268
+ lmLogits = {};
269
+ 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;
270
+ if (logprobsContent && logprobsContent.length > 0) {
271
+ tokenLogprobs = logprobsContent[0]; // Add the top token
272
+ if (tokenLogprobs.token) {
273
+ token = tokenLogprobs.token.trim().toLowerCase();
274
+ if (token.length > 0 && /^[a-z\u017F\u212A]/i.test(token)) {
275
+ lmLogits[token] = Math.exp(tokenLogprobs.logprob);
276
+ }
277
+ }
278
+
279
+ // Add alternative tokens from top_logprobs
280
+ if (tokenLogprobs.top_logprobs) {
281
+ _iterator = _createForOfIteratorHelper(tokenLogprobs.top_logprobs);
282
+ try {
283
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
284
+ alt = _step.value;
285
+ _token = alt.token.trim().toLowerCase();
286
+ if (_token.length > 0 && /^[a-z\u017F\u212A]/i.test(_token)) {
287
+ lmLogits[_token] = Math.exp(alt.logprob);
288
+ }
289
+ }
290
+ } catch (err) {
291
+ _iterator.e(err);
292
+ } finally {
293
+ _iterator.f();
294
+ }
295
+ }
296
+ }
297
+ storedLmLogits = Object.keys(lmLogits).length > 0 ? lmLogits : null;
298
+
299
+ // ── Semantic vector ─────────────────────────────────────────
300
+ // SmolLM is a generative model, not an embedding model, so we
301
+ // don't get a true semantic vector. We generate a lightweight
302
+ // pseudo-embedding from the logit distribution for compatibility
303
+ // with the existing scoring pipeline.
304
+ //
305
+ // For a production implementation, you would use a dedicated
306
+ // embedding model (e.g. via web-llm's embeddings API with an
307
+ // embedding-specific model).
308
+ if (storedLmLogits) {
309
+ logitValues = Object.values(storedLmLogits);
310
+ storedContextVector = new Float32Array(logitValues);
311
+ } else {
312
+ storedContextVector = null;
313
+ }
314
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
315
+ // eslint-disable-next-line no-console
316
+ console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE5 Inference result (request #".concat(requestId, ")"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
317
+ // eslint-disable-next-line no-console
318
+ console.log(storedContextVector ? "\u2705 pseudo-vector: ".concat(storedContextVector.length, " dims") : '❌ No vector');
319
+ // eslint-disable-next-line no-console
320
+ console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
321
+ if (storedLmLogits) {
322
+ topTokens = Object.entries(storedLmLogits).sort(function (_ref3, _ref4) {
323
+ var _ref5 = (0, _slicedToArray2.default)(_ref3, 2),
324
+ a = _ref5[1];
325
+ var _ref6 = (0, _slicedToArray2.default)(_ref4, 2),
326
+ b = _ref6[1];
327
+ return b - a;
328
+ }).slice(0, 10); // eslint-disable-next-line no-console
329
+ console.log('Top 10 predictions:', topTokens.map(function (_ref7) {
330
+ var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
331
+ t = _ref8[0],
332
+ p = _ref8[1];
333
+ return "".concat(t, ": ").concat((p * 100).toFixed(1), "%");
334
+ }).join(', '));
335
+ }
336
+ // eslint-disable-next-line no-console
337
+ console.groupEnd();
338
+ }
339
+ onUpdate === null || onUpdate === void 0 || onUpdate({
340
+ textLength: text.length,
341
+ hasVector: storedContextVector !== null,
342
+ hasLmLogits: storedLmLogits !== null
343
+ });
344
+ _context2.next = 26;
345
+ break;
346
+ case 17:
347
+ _context2.prev = 17;
348
+ _context2.t0 = _context2["catch"](2);
349
+ if (!(requestId < latestRequestId)) {
350
+ _context2.next = 21;
351
+ break;
352
+ }
353
+ return _context2.abrupt("return");
354
+ case 21:
355
+ storedContextVector = null;
356
+ storedLmLogits = null;
357
+ onUpdate === null || onUpdate === void 0 || onUpdate({
358
+ textLength: text.length,
359
+ hasVector: false,
360
+ hasLmLogits: false
361
+ });
362
+ errorMsg = _context2.t0 instanceof Error ? _context2.t0.message : String(_context2.t0);
363
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
364
+ // eslint-disable-next-line no-console
365
+ console.log("%c[LocalSlowLane] %c\u274C Inference error (request #".concat(requestId, "): ").concat(errorMsg), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
366
+ }
367
+ case 26:
368
+ case "end":
369
+ return _context2.stop();
370
+ }
371
+ }, _callee2, null, [[2, 17]]);
372
+ }));
373
+ return function runInference(_x, _x2) {
374
+ return _ref2.apply(this, arguments);
375
+ };
376
+ }();
377
+
378
+ // ── Context update (debounced) ─────────────────────────────────────────
379
+
380
+ var doUpdateContext = function doUpdateContext(text) {
381
+ if (destroyed || !text || text.trim().length === 0) {
382
+ return;
383
+ }
384
+ var requestId = ++requestCounter;
385
+ latestRequestId = requestId;
386
+ if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
387
+ // eslint-disable-next-line no-console
388
+ console.groupCollapsed("%c[LocalSlowLane] %c\uD83D\uDCE4 Context update (request #".concat(requestId, ") | ").concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
389
+ var lines = text.split('\n');
390
+ lines.forEach(function (line, i) {
391
+ // eslint-disable-next-line no-console
392
+ console.log(" ".concat(i === lines.length - 1 ? '▶' : ' ', " ").concat(line));
393
+ });
394
+ // eslint-disable-next-line no-console
395
+ console.groupEnd();
396
+ }
397
+ void ensureEngineInitialized().then(function () {
398
+ return runInference(text, requestId);
399
+ }).catch(function () {});
400
+ };
401
+ var updateContextDebounced = function updateContextDebounced(text) {
402
+ if (debounceTimer) {
403
+ clearTimeout(debounceTimer);
404
+ }
405
+ lastRequestedText = text;
406
+ debounceTimer = setTimeout(function () {
407
+ debounceTimer = null;
408
+ doUpdateContext(lastRequestedText);
409
+ }, debounceMs);
410
+ };
411
+
412
+ // ── Public API (same shape as createSlowLaneClient) ────────────────────
413
+ return {
414
+ updateContext: updateContextDebounced,
415
+ getContextVector: function getContextVector() {
416
+ return storedContextVector;
417
+ },
418
+ getLmLogits: function getLmLogits() {
419
+ return storedLmLogits;
420
+ },
421
+ setContextVector: function setContextVector(vector) {
422
+ storedContextVector = vector;
423
+ },
424
+ setLmLogits: function setLmLogits(logits) {
425
+ storedLmLogits = logits;
426
+ },
427
+ isWordBoundary: _slowLaneClient.isWordBoundary,
428
+ isReady: function isReady() {
429
+ return ready;
430
+ },
431
+ destroy: function destroy() {
432
+ destroyed = true;
433
+ ready = false;
434
+ if (debounceTimer) {
435
+ clearTimeout(debounceTimer);
436
+ }
437
+ if (engine) {
438
+ var engineToUnload = engine;
439
+ engine = null;
440
+ unloadEngine(engineToUnload);
441
+ }
442
+ engineInitPromise = null;
443
+ storedContextVector = null;
444
+ storedLmLogits = null;
445
+ }
446
+ };
447
+ };
@@ -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 {