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