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