@atlaskit/editor-plugin-autocomplete 3.5.0 → 3.6.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.
@@ -43,6 +43,19 @@ import { isWordBoundary } from './slow-lane-client';
43
43
 
44
44
  // Same return type as createSlowLaneClient for drop-in compatibility
45
45
 
46
+ /**
47
+ * Why the local engine failed to load/start.
48
+ *
49
+ * The first three are user-machine limitations (WebGPU missing, no compatible
50
+ * GPU adapter, GPU lacks the `shader-f16` feature the model needs);
51
+ * `insufficient_memory` is hit when weights don't fit in VRAM. The rest cover
52
+ * delivery/runtime failures unrelated to hardware.
53
+ */
54
+
55
+ /** Snapshot of the machine's WebGPU support, used to explain hardware limits. */
56
+
57
+ // Minimal WebGPU shape: lib.dom types aren't guaranteed in this build target.
58
+
46
59
  // ─── Constants ───────────────────────────────────────────────────────────────
47
60
 
48
61
  const DEFAULT_DEBOUNCE_MS = 300;
@@ -470,6 +483,8 @@ export const createLocalSlowLaneClient = (config = {}) => {
470
483
  debounceMs = DEFAULT_DEBOUNCE_MS,
471
484
  onUpdate,
472
485
  onStatus,
486
+ onLoadError,
487
+ onLoadSuccess,
473
488
  modelId = LOCAL_MLC_CAUSAL_MODEL_ID,
474
489
  customModelConfig
475
490
  } = config;
@@ -516,16 +531,129 @@ export const createLocalSlowLaneClient = (config = {}) => {
516
531
  }
517
532
  onStatus === null || onStatus === void 0 ? void 0 : onStatus(message);
518
533
  };
534
+ const bytesToMB = bytes => typeof bytes === 'number' ? Math.round(bytes / (1024 * 1024)) : undefined;
535
+
536
+ /**
537
+ * Inspect the machine's WebGPU support so a load failure can be attributed
538
+ * to a concrete hardware/browser limitation rather than a generic error.
539
+ */
540
+ const probeWebGpuCapabilities = async () => {
541
+ const gpu = navigator.gpu;
542
+ if (!gpu) {
543
+ return {
544
+ available: false
545
+ };
546
+ }
547
+ try {
548
+ var _adapter$limits, _adapter$limits2;
549
+ const adapter = await gpu.requestAdapter();
550
+ if (!adapter) {
551
+ return {
552
+ available: true,
553
+ adapterAvailable: false
554
+ };
555
+ }
556
+ let vendor;
557
+ let architecture;
558
+ try {
559
+ var _adapter$info, _adapter$requestAdapt;
560
+ const info = (_adapter$info = adapter.info) !== null && _adapter$info !== void 0 ? _adapter$info : await ((_adapter$requestAdapt = adapter.requestAdapterInfo) === null || _adapter$requestAdapt === void 0 ? void 0 : _adapter$requestAdapt.call(adapter));
561
+ vendor = (info === null || info === void 0 ? void 0 : info.vendor) || undefined;
562
+ architecture = (info === null || info === void 0 ? void 0 : info.architecture) || undefined;
563
+ } catch {
564
+ // adapter info is best-effort
565
+ }
566
+ return {
567
+ available: true,
568
+ adapterAvailable: true,
569
+ shaderF16Supported: adapter.features.has('shader-f16'),
570
+ maxBufferSizeMB: bytesToMB((_adapter$limits = adapter.limits) === null || _adapter$limits === void 0 ? void 0 : _adapter$limits.maxBufferSize),
571
+ maxStorageBufferBindingSizeMB: bytesToMB((_adapter$limits2 = adapter.limits) === null || _adapter$limits2 === void 0 ? void 0 : _adapter$limits2.maxStorageBufferBindingSize),
572
+ vendor,
573
+ architecture
574
+ };
575
+ } catch {
576
+ return {
577
+ available: true,
578
+ adapterAvailable: false
579
+ };
580
+ }
581
+ };
582
+
583
+ /** Map an MLC/WebLLM engine-creation error message to a coarse reason. */
584
+ const classifyEngineError = message => {
585
+ const lower = message.toLowerCase();
586
+ if (lower.includes('loading chunk') || lower.includes('dynamically imported module') || lower.includes('dynamic import')) {
587
+ return 'module_load_failed';
588
+ }
589
+ if (lower.includes('out of memory') || /\boom\b/u.test(lower) || lower.includes('allocation') || lower.includes('exceeds') || lower.includes('buffer size') || lower.includes('not enough memory')) {
590
+ return 'insufficient_memory';
591
+ }
592
+ // Pre-flight already returns missing_shader_f16 when the feature is absent,
593
+ // so only match the exact feature token here — not bare 'shader' (compile
594
+ // errors) or bare 'f16' (present in model ids like q0f16-MLC).
595
+ if (lower.includes('shader-f16') || lower.includes('shader_f16')) {
596
+ return 'missing_shader_f16';
597
+ }
598
+ if (lower.includes('fetch') || lower.includes('network') || lower.includes('download') || lower.includes('http') || lower.includes('cache')) {
599
+ return 'model_download_failed';
600
+ }
601
+ return 'init_failed';
602
+ };
603
+
604
+ // Canonical, controlled failure descriptions. We never emit the raw engine
605
+ // error into analytics — it can embed customer-context URLs/paths (HOT-120175)
606
+ // — so the analytics `message` is always one of these fixed strings.
607
+ const LOAD_FAILURE_MESSAGE = {
608
+ webgpu_unavailable: 'WebGPU is not available in this browser',
609
+ webgpu_no_adapter: 'No compatible WebGPU adapter found',
610
+ missing_shader_f16: 'GPU does not support the shader-f16 feature',
611
+ insufficient_memory: 'Insufficient GPU memory to load the model',
612
+ model_download_failed: 'Failed to download model assets',
613
+ module_load_failed: 'Failed to load the web-llm runtime module',
614
+ init_failed: 'Model engine failed to initialise'
615
+ };
616
+ const handleLoadFailure = (reason, capabilities, debugDetail) => {
617
+ ready = false;
618
+ const message = LOAD_FAILURE_MESSAGE[reason];
619
+ if (isAutocompleteDebugEnabled()) {
620
+ // eslint-disable-next-line no-console
621
+ console.log(`[LocalSlowLane] Engine initialisation failed (${reason}): ${debugDetail !== null && debugDetail !== void 0 ? debugDetail : message}`);
622
+ }
623
+ onStatus === null || onStatus === void 0 ? void 0 : onStatus(`Engine initialisation failed: ${message}`);
624
+ onLoadError === null || onLoadError === void 0 ? void 0 : onLoadError({
625
+ reason,
626
+ message,
627
+ modelId,
628
+ embeddingModelId: LOCAL_MLC_EMBEDDING_MODEL_ID,
629
+ capabilities
630
+ });
631
+ engineInitPromise = null;
632
+ initFailed = true;
633
+ };
519
634
  const initEngine = async () => {
635
+ const capabilities = await probeWebGpuCapabilities();
636
+
637
+ // ── Pre-flight: machine limitations short-circuit before the expensive load ──
638
+ if (!capabilities.available) {
639
+ handleLoadFailure('webgpu_unavailable', capabilities);
640
+ return;
641
+ }
642
+ if (capabilities.adapterAvailable === false) {
643
+ handleLoadFailure('webgpu_no_adapter', capabilities);
644
+ return;
645
+ }
646
+ if (capabilities.shaderF16Supported === false) {
647
+ handleLoadFailure('missing_shader_f16', capabilities);
648
+ return;
649
+ }
650
+ const startTime = performance.now();
520
651
  try {
521
652
  if (isAutocompleteDebugEnabled()) {
522
653
  // eslint-disable-next-line no-console
523
654
  console.log(`%c[LocalSlowLane] %c🚀 Initialising MLC engine with models: ${modelId} (LM) + ${LOCAL_MLC_EMBEDDING_MODEL_ID} (embedder)`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
524
655
  }
525
656
  onStatus === null || onStatus === void 0 ? void 0 : onStatus(`Initialising models: ${modelId} + ${LOCAL_MLC_EMBEDDING_MODEL_ID}…`);
526
- if (!('gpu' in navigator)) {
527
- throw new Error('WebGPU not supported');
528
- }
529
657
 
530
658
  // Fetch the web-llm runtime and the BE-parity lookup tables in parallel;
531
659
  // both are dynamically imported so they stay out of the main editor chunk.
@@ -568,6 +696,7 @@ export const createLocalSlowLaneClient = (config = {}) => {
568
696
  }
569
697
  engine = newEngine;
570
698
  ready = true;
699
+ const loadDurationMs = Math.round(performance.now() - startTime);
571
700
  if (isAutocompleteDebugEnabled()) {
572
701
  // eslint-disable-next-line no-console
573
702
  console.log('%c[LocalSlowLane] %c✅ Both models loaded and ready', 'color: #9c27b0; font-weight: bold;', 'color: #4caf50;');
@@ -579,16 +708,15 @@ export const createLocalSlowLaneClient = (config = {}) => {
579
708
  console.log('%c[LocalSlowLane] %c🔢 Embedder →', 'color: #9c27b0; font-weight: bold;', 'color: #009688; font-weight: bold;', LOCAL_MLC_EMBEDDING_MODEL_ID);
580
709
  }
581
710
  onStatus === null || onStatus === void 0 ? void 0 : onStatus('Model loaded and ready.');
711
+ onLoadSuccess === null || onLoadSuccess === void 0 ? void 0 : onLoadSuccess({
712
+ modelId,
713
+ embeddingModelId: LOCAL_MLC_EMBEDDING_MODEL_ID,
714
+ loadDurationMs,
715
+ capabilities
716
+ });
582
717
  } catch (err) {
583
718
  const errorMsg = err instanceof Error ? err.message : String(err);
584
- ready = false;
585
- if (isAutocompleteDebugEnabled()) {
586
- // eslint-disable-next-line no-console
587
- console.log(`[LocalSlowLane] Engine initialisation failed: ${errorMsg}`);
588
- }
589
- onStatus === null || onStatus === void 0 ? void 0 : onStatus(`Engine initialisation failed: ${errorMsg}`);
590
- engineInitPromise = null;
591
- initFailed = true;
719
+ handleLoadFailure(classifyEngineError(errorMsg), capabilities, errorMsg);
592
720
  }
593
721
  };
594
722
  const ensureEngineInitialized = () => {
@@ -240,12 +240,49 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
240
240
  }
241
241
  });
242
242
  };
243
- var fireSuggestionInsertedAnalytics = function fireSuggestionInsertedAnalytics(ghostText) {
243
+ var fireLocalModelLoadedAnalytics = function fireLocalModelLoadedAnalytics(info) {
244
244
  var _api$analytics2;
245
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
246
+ action: ACTION.LOCAL_MODEL_LOADED,
247
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
248
+ eventType: EVENT_TYPE.TRACK,
249
+ attributes: {
250
+ modelId: info.modelId,
251
+ embeddingModelId: info.embeddingModelId,
252
+ loadDurationMs: info.loadDurationMs,
253
+ gpuVendor: info.capabilities.vendor,
254
+ gpuArchitecture: info.capabilities.architecture
255
+ }
256
+ });
257
+ };
258
+ var fireLocalModelLoadFailedAnalytics = function fireLocalModelLoadFailedAnalytics(error) {
259
+ var _api$analytics3;
260
+ var capabilities = error.capabilities;
261
+ api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.fireAnalyticsEvent({
262
+ action: ACTION.LOCAL_MODEL_LOAD_FAILED,
263
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
264
+ eventType: EVENT_TYPE.TRACK,
265
+ attributes: {
266
+ reason: error.reason,
267
+ message: error.message,
268
+ modelId: error.modelId,
269
+ embeddingModelId: error.embeddingModelId,
270
+ webgpuAvailable: capabilities.available,
271
+ adapterAvailable: capabilities.adapterAvailable,
272
+ shaderF16Supported: capabilities.shaderF16Supported,
273
+ maxBufferSizeMB: capabilities.maxBufferSizeMB,
274
+ maxStorageBufferBindingSizeMB: capabilities.maxStorageBufferBindingSizeMB,
275
+ gpuVendor: capabilities.vendor,
276
+ gpuArchitecture: capabilities.architecture
277
+ }
278
+ });
279
+ };
280
+ var fireSuggestionInsertedAnalytics = function fireSuggestionInsertedAnalytics(ghostText) {
281
+ var _api$analytics4;
245
282
  var typedLength = lastSuggestionTypedLength;
246
283
  var suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
247
284
  var kssDelta = suggestionLength - typedLength;
248
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
285
+ api === null || api === void 0 || (_api$analytics4 = api.analytics) === null || _api$analytics4 === void 0 || _api$analytics4.actions.fireAnalyticsEvent({
249
286
  action: ACTION.SUGGESTION_INSERTED,
250
287
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
251
288
  eventType: EVENT_TYPE.TRACK,
@@ -258,7 +295,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
258
295
  });
259
296
  };
260
297
  var slowLaneClient = options !== null && options !== void 0 && options.useLocalModel ? createLocalSlowLaneClient({
261
- debounceMs: LOCAL_SLOW_LANE_DEBOUNCE_MS
298
+ debounceMs: LOCAL_SLOW_LANE_DEBOUNCE_MS,
299
+ onLoadSuccess: fireLocalModelLoadedAnalytics,
300
+ onLoadError: fireLocalModelLoadFailedAnalytics
262
301
  }) : createSlowLaneClient({
263
302
  baseUrl: '',
264
303
  debounceMs: NETWORK_SLOW_LANE_DEBOUNCE_MS
@@ -412,9 +451,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
412
451
  lastSuggestionLength = typedLength + prediction.length;
413
452
  showGhostText(view, prediction, selection.from);
414
453
  if (prediction !== lastShownGhostText) {
415
- var _api$analytics3;
454
+ var _api$analytics5;
416
455
  lastShownGhostText = prediction;
417
- api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.fireAnalyticsEvent({
456
+ api === null || api === void 0 || (_api$analytics5 = api.analytics) === null || _api$analytics5 === void 0 || _api$analytics5.actions.fireAnalyticsEvent({
418
457
  action: ACTION.SUGGESTION_VIEWED,
419
458
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
420
459
  eventType: EVENT_TYPE.TRACK,