@atlaskit/editor-plugin-autocomplete 3.5.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +50 -7
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +241 -63
- package/dist/cjs/pm-plugins/text-predictor.js +18 -4
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +52 -7
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +141 -11
- package/dist/es2019/pm-plugins/text-predictor.js +17 -3
- package/dist/esm/pm-plugins/autocomplete-plugin.js +50 -7
- package/dist/esm/pm-plugins/local-slow-lane-client.js +240 -62
- package/dist/esm/pm-plugins/text-predictor.js +18 -4
- package/dist/types/pm-plugins/local-slow-lane-client.d.ts +52 -0
- package/dist/types/pm-plugins/text-predictor.d.ts +4 -1
- package/dist/types-ts4.5/pm-plugins/local-slow-lane-client.d.ts +52 -0
- package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +4 -1
- package/package.json +2 -2
- package/src/pm-plugins/autocomplete-plugin.ts +57 -10
- package/src/pm-plugins/local-slow-lane-client.ts +224 -12
- package/src/pm-plugins/text-predictor.ts +12 -6
|
@@ -60,6 +60,10 @@ export interface LocalSlowLaneClientConfig {
|
|
|
60
60
|
* `customModelConfig` with the model URL and WASM library URL.
|
|
61
61
|
*/
|
|
62
62
|
modelId?: string;
|
|
63
|
+
/** Callback fired when the engine fails to load/start. */
|
|
64
|
+
onLoadError?: (error: LocalSlowLaneLoadError) => void;
|
|
65
|
+
/** Callback fired when the engine successfully loads and is ready. */
|
|
66
|
+
onLoadSuccess?: (info: LocalSlowLaneLoadSuccess) => void;
|
|
63
67
|
/** Callback fired with status messages (model loading progress, etc.). */
|
|
64
68
|
onStatus?: (message: string) => void;
|
|
65
69
|
/** Callback fired when inference returns new results. */
|
|
@@ -81,6 +85,54 @@ export interface LocalSlowLaneClient {
|
|
|
81
85
|
setLmLogits: (logits: Record<string, number> | null) => void;
|
|
82
86
|
updateContext: (text: string) => void;
|
|
83
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Why the local engine failed to load/start.
|
|
90
|
+
*
|
|
91
|
+
* The first three are user-machine limitations (WebGPU missing, no compatible
|
|
92
|
+
* GPU adapter, GPU lacks the `shader-f16` feature the model needs);
|
|
93
|
+
* `insufficient_memory` is hit when weights don't fit in VRAM. The rest cover
|
|
94
|
+
* delivery/runtime failures unrelated to hardware.
|
|
95
|
+
*/
|
|
96
|
+
export type LocalSlowLaneLoadErrorReason = 'webgpu_unavailable' | 'webgpu_no_adapter' | 'missing_shader_f16' | 'insufficient_memory' | 'model_download_failed' | 'module_load_failed' | 'init_failed';
|
|
97
|
+
/** Snapshot of the machine's WebGPU support, used to explain hardware limits. */
|
|
98
|
+
export interface WebGpuCapabilities {
|
|
99
|
+
/** Whether `navigator.gpu.requestAdapter()` returned a usable adapter. */
|
|
100
|
+
adapterAvailable?: boolean;
|
|
101
|
+
/** GPU architecture reported by the adapter (e.g. "metal-3", "rdna2"). */
|
|
102
|
+
architecture?: string;
|
|
103
|
+
/** Whether `navigator.gpu` exists at all. */
|
|
104
|
+
available: boolean;
|
|
105
|
+
/** Largest single GPU buffer the adapter allows, in MB. */
|
|
106
|
+
maxBufferSizeMB?: number;
|
|
107
|
+
/** Largest storage-buffer binding the adapter allows, in MB. */
|
|
108
|
+
maxStorageBufferBindingSizeMB?: number;
|
|
109
|
+
/** Whether the adapter exposes the `shader-f16` feature the model requires. */
|
|
110
|
+
shaderF16Supported?: boolean;
|
|
111
|
+
/** GPU vendor reported by the adapter (e.g. "apple", "intel"). */
|
|
112
|
+
vendor?: string;
|
|
113
|
+
}
|
|
114
|
+
export interface LocalSlowLaneLoadError {
|
|
115
|
+
/** WebGPU support snapshot — explains hardware limitations behind the failure. */
|
|
116
|
+
capabilities: WebGpuCapabilities;
|
|
117
|
+
/** Semantic embedder loaded alongside the causal LM (loads atomically). */
|
|
118
|
+
embeddingModelId: string;
|
|
119
|
+
/** Canonical, controlled failure description (never raw error text). */
|
|
120
|
+
message: string;
|
|
121
|
+
/** Causal LM identifier that failed to load. */
|
|
122
|
+
modelId: string;
|
|
123
|
+
/** Coarse, privacy-safe failure category. */
|
|
124
|
+
reason: LocalSlowLaneLoadErrorReason;
|
|
125
|
+
}
|
|
126
|
+
export interface LocalSlowLaneLoadSuccess {
|
|
127
|
+
/** WebGPU support snapshot for the machine that loaded the model. */
|
|
128
|
+
capabilities: WebGpuCapabilities;
|
|
129
|
+
/** Semantic embedder loaded alongside the causal LM (loads atomically). */
|
|
130
|
+
embeddingModelId: string;
|
|
131
|
+
/** Model engine load time in ms (excludes the WebGPU capability probe). */
|
|
132
|
+
loadDurationMs: number;
|
|
133
|
+
/** Causal LM identifier that loaded. */
|
|
134
|
+
modelId: string;
|
|
135
|
+
}
|
|
84
136
|
export declare const LOCAL_MLC_CAUSAL_MODEL_ID = "SmolLM2-135M-Instruct-q0f16-MLC";
|
|
85
137
|
/**
|
|
86
138
|
* MLC ID for the semantic embedder (Snowflake Arctic Embed S, batch=4 variant).
|
|
@@ -82,7 +82,10 @@ export declare const ingestDocumentPage: (pageContent: string | undefined) => vo
|
|
|
82
82
|
export declare const predict: (textBefore: string) => string | null;
|
|
83
83
|
export declare const loadVectorsAsync: (options?: {
|
|
84
84
|
getBinaryUrl?: () => Promise<string>;
|
|
85
|
+
isLocalLLM?: boolean;
|
|
85
86
|
}) => Promise<void>;
|
|
86
87
|
export declare const initVectors: (store: VectorStore) => void;
|
|
87
|
-
export declare const loadDefaultVocabulary: (
|
|
88
|
+
export declare const loadDefaultVocabulary: (options?: {
|
|
89
|
+
isLocalLLM?: boolean;
|
|
90
|
+
}) => Promise<void>;
|
|
88
91
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"wink-nlp": "^2.4.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@atlaskit/editor-common": "^115.
|
|
38
|
+
"@atlaskit/editor-common": "^115.15.0",
|
|
39
39
|
"@atlaskit/editor-plugin-analytics": "^11.0.0",
|
|
40
40
|
"react": "^18.2.0"
|
|
41
41
|
},
|
|
@@ -16,7 +16,12 @@ import type { AutocompletePlugin } from '../autocompletePluginType';
|
|
|
16
16
|
|
|
17
17
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
18
18
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createLocalSlowLaneClient,
|
|
21
|
+
type LocalSlowLaneClient,
|
|
22
|
+
type LocalSlowLaneLoadError,
|
|
23
|
+
type LocalSlowLaneLoadSuccess,
|
|
24
|
+
} from './local-slow-lane-client';
|
|
20
25
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
21
26
|
import {
|
|
22
27
|
predict,
|
|
@@ -298,6 +303,43 @@ export const createAutocompletePlugin = (
|
|
|
298
303
|
});
|
|
299
304
|
};
|
|
300
305
|
|
|
306
|
+
const fireLocalModelLoadedAnalytics = (info: LocalSlowLaneLoadSuccess): void => {
|
|
307
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
308
|
+
action: ACTION.LOCAL_MODEL_LOADED,
|
|
309
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
310
|
+
eventType: EVENT_TYPE.TRACK,
|
|
311
|
+
attributes: {
|
|
312
|
+
modelId: info.modelId,
|
|
313
|
+
embeddingModelId: info.embeddingModelId,
|
|
314
|
+
loadDurationMs: info.loadDurationMs,
|
|
315
|
+
gpuVendor: info.capabilities.vendor,
|
|
316
|
+
gpuArchitecture: info.capabilities.architecture,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const fireLocalModelLoadFailedAnalytics = (error: LocalSlowLaneLoadError): void => {
|
|
322
|
+
const { capabilities } = error;
|
|
323
|
+
api?.analytics?.actions.fireAnalyticsEvent({
|
|
324
|
+
action: ACTION.LOCAL_MODEL_LOAD_FAILED,
|
|
325
|
+
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
326
|
+
eventType: EVENT_TYPE.TRACK,
|
|
327
|
+
attributes: {
|
|
328
|
+
reason: error.reason,
|
|
329
|
+
message: error.message,
|
|
330
|
+
modelId: error.modelId,
|
|
331
|
+
embeddingModelId: error.embeddingModelId,
|
|
332
|
+
webgpuAvailable: capabilities.available,
|
|
333
|
+
adapterAvailable: capabilities.adapterAvailable,
|
|
334
|
+
shaderF16Supported: capabilities.shaderF16Supported,
|
|
335
|
+
maxBufferSizeMB: capabilities.maxBufferSizeMB,
|
|
336
|
+
maxStorageBufferBindingSizeMB: capabilities.maxStorageBufferBindingSizeMB,
|
|
337
|
+
gpuVendor: capabilities.vendor,
|
|
338
|
+
gpuArchitecture: capabilities.architecture,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
301
343
|
const fireSuggestionInsertedAnalytics = (ghostText: string): void => {
|
|
302
344
|
const typedLength = lastSuggestionTypedLength;
|
|
303
345
|
const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
|
|
@@ -319,6 +361,8 @@ export const createAutocompletePlugin = (
|
|
|
319
361
|
const slowLaneClient = options?.useLocalModel
|
|
320
362
|
? createLocalSlowLaneClient({
|
|
321
363
|
debounceMs: LOCAL_SLOW_LANE_DEBOUNCE_MS,
|
|
364
|
+
onLoadSuccess: fireLocalModelLoadedAnalytics,
|
|
365
|
+
onLoadError: fireLocalModelLoadFailedAnalytics,
|
|
322
366
|
})
|
|
323
367
|
: createSlowLaneClient({
|
|
324
368
|
baseUrl: '',
|
|
@@ -636,17 +680,20 @@ export const createAutocompletePlugin = (
|
|
|
636
680
|
}
|
|
637
681
|
return false;
|
|
638
682
|
},
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
});
|
|
683
|
+
focus: () => {
|
|
684
|
+
loadDefaultVocabulary({ isLocalLLM: options?.useLocalModel ?? false }).catch((error) => {
|
|
685
|
+
logException(error as Error, {
|
|
686
|
+
location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
|
|
644
687
|
});
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
688
|
+
});
|
|
689
|
+
loadVectorsAsync({
|
|
690
|
+
getBinaryUrl: options?.getVectorsBinaryUrl,
|
|
691
|
+
isLocalLLM: options?.useLocalModel ?? false,
|
|
692
|
+
}).catch((error) => {
|
|
693
|
+
logException(error as Error, {
|
|
694
|
+
location: 'editor-plugin-autocomplete/loadVectorsAsync',
|
|
649
695
|
});
|
|
696
|
+
});
|
|
650
697
|
if (!hasIngestedPage) {
|
|
651
698
|
hasIngestedPage = true;
|
|
652
699
|
refreshContext({
|
|
@@ -71,6 +71,10 @@ export interface LocalSlowLaneClientConfig {
|
|
|
71
71
|
* `customModelConfig` with the model URL and WASM library URL.
|
|
72
72
|
*/
|
|
73
73
|
modelId?: string;
|
|
74
|
+
/** Callback fired when the engine fails to load/start. */
|
|
75
|
+
onLoadError?: (error: LocalSlowLaneLoadError) => void;
|
|
76
|
+
/** Callback fired when the engine successfully loads and is ready. */
|
|
77
|
+
onLoadSuccess?: (info: LocalSlowLaneLoadSuccess) => void;
|
|
74
78
|
/** Callback fired with status messages (model loading progress, etc.). */
|
|
75
79
|
onStatus?: (message: string) => void;
|
|
76
80
|
/** Callback fired when inference returns new results. */
|
|
@@ -91,6 +95,80 @@ export interface LocalSlowLaneClient {
|
|
|
91
95
|
updateContext: (text: string) => void;
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Why the local engine failed to load/start.
|
|
100
|
+
*
|
|
101
|
+
* The first three are user-machine limitations (WebGPU missing, no compatible
|
|
102
|
+
* GPU adapter, GPU lacks the `shader-f16` feature the model needs);
|
|
103
|
+
* `insufficient_memory` is hit when weights don't fit in VRAM. The rest cover
|
|
104
|
+
* delivery/runtime failures unrelated to hardware.
|
|
105
|
+
*/
|
|
106
|
+
export type LocalSlowLaneLoadErrorReason =
|
|
107
|
+
| 'webgpu_unavailable'
|
|
108
|
+
| 'webgpu_no_adapter'
|
|
109
|
+
| 'missing_shader_f16'
|
|
110
|
+
| 'insufficient_memory'
|
|
111
|
+
| 'model_download_failed'
|
|
112
|
+
| 'module_load_failed'
|
|
113
|
+
| 'init_failed';
|
|
114
|
+
|
|
115
|
+
/** Snapshot of the machine's WebGPU support, used to explain hardware limits. */
|
|
116
|
+
export interface WebGpuCapabilities {
|
|
117
|
+
/** Whether `navigator.gpu.requestAdapter()` returned a usable adapter. */
|
|
118
|
+
adapterAvailable?: boolean;
|
|
119
|
+
/** GPU architecture reported by the adapter (e.g. "metal-3", "rdna2"). */
|
|
120
|
+
architecture?: string;
|
|
121
|
+
/** Whether `navigator.gpu` exists at all. */
|
|
122
|
+
available: boolean;
|
|
123
|
+
/** Largest single GPU buffer the adapter allows, in MB. */
|
|
124
|
+
maxBufferSizeMB?: number;
|
|
125
|
+
/** Largest storage-buffer binding the adapter allows, in MB. */
|
|
126
|
+
maxStorageBufferBindingSizeMB?: number;
|
|
127
|
+
/** Whether the adapter exposes the `shader-f16` feature the model requires. */
|
|
128
|
+
shaderF16Supported?: boolean;
|
|
129
|
+
/** GPU vendor reported by the adapter (e.g. "apple", "intel"). */
|
|
130
|
+
vendor?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface LocalSlowLaneLoadError {
|
|
134
|
+
/** WebGPU support snapshot — explains hardware limitations behind the failure. */
|
|
135
|
+
capabilities: WebGpuCapabilities;
|
|
136
|
+
/** Semantic embedder loaded alongside the causal LM (loads atomically). */
|
|
137
|
+
embeddingModelId: string;
|
|
138
|
+
/** Canonical, controlled failure description (never raw error text). */
|
|
139
|
+
message: string;
|
|
140
|
+
/** Causal LM identifier that failed to load. */
|
|
141
|
+
modelId: string;
|
|
142
|
+
/** Coarse, privacy-safe failure category. */
|
|
143
|
+
reason: LocalSlowLaneLoadErrorReason;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface LocalSlowLaneLoadSuccess {
|
|
147
|
+
/** WebGPU support snapshot for the machine that loaded the model. */
|
|
148
|
+
capabilities: WebGpuCapabilities;
|
|
149
|
+
/** Semantic embedder loaded alongside the causal LM (loads atomically). */
|
|
150
|
+
embeddingModelId: string;
|
|
151
|
+
/** Model engine load time in ms (excludes the WebGPU capability probe). */
|
|
152
|
+
loadDurationMs: number;
|
|
153
|
+
/** Causal LM identifier that loaded. */
|
|
154
|
+
modelId: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Minimal WebGPU shape: lib.dom types aren't guaranteed in this build target.
|
|
158
|
+
interface MinimalGpuAdapterInfo {
|
|
159
|
+
architecture?: string;
|
|
160
|
+
vendor?: string;
|
|
161
|
+
}
|
|
162
|
+
interface MinimalGpuAdapter {
|
|
163
|
+
features: { has: (feature: string) => boolean };
|
|
164
|
+
info?: MinimalGpuAdapterInfo;
|
|
165
|
+
limits?: { maxBufferSize?: number; maxStorageBufferBindingSize?: number };
|
|
166
|
+
requestAdapterInfo?: () => Promise<MinimalGpuAdapterInfo>;
|
|
167
|
+
}
|
|
168
|
+
interface MinimalGpu {
|
|
169
|
+
requestAdapter: () => Promise<MinimalGpuAdapter | null>;
|
|
170
|
+
}
|
|
171
|
+
|
|
94
172
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
95
173
|
|
|
96
174
|
const DEFAULT_DEBOUNCE_MS = 300;
|
|
@@ -584,6 +662,8 @@ export const createLocalSlowLaneClient = (
|
|
|
584
662
|
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
585
663
|
onUpdate,
|
|
586
664
|
onStatus,
|
|
665
|
+
onLoadError,
|
|
666
|
+
onLoadSuccess,
|
|
587
667
|
modelId = LOCAL_MLC_CAUSAL_MODEL_ID,
|
|
588
668
|
customModelConfig,
|
|
589
669
|
} = config;
|
|
@@ -641,7 +721,143 @@ export const createLocalSlowLaneClient = (
|
|
|
641
721
|
onStatus?.(message);
|
|
642
722
|
};
|
|
643
723
|
|
|
724
|
+
const bytesToMB = (bytes?: number): number | undefined =>
|
|
725
|
+
typeof bytes === 'number' ? Math.round(bytes / (1024 * 1024)) : undefined;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Inspect the machine's WebGPU support so a load failure can be attributed
|
|
729
|
+
* to a concrete hardware/browser limitation rather than a generic error.
|
|
730
|
+
*/
|
|
731
|
+
const probeWebGpuCapabilities = async (): Promise<WebGpuCapabilities> => {
|
|
732
|
+
const gpu = (navigator as Navigator & { gpu?: MinimalGpu }).gpu;
|
|
733
|
+
if (!gpu) {
|
|
734
|
+
return { available: false };
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
const adapter = await gpu.requestAdapter();
|
|
738
|
+
if (!adapter) {
|
|
739
|
+
return { available: true, adapterAvailable: false };
|
|
740
|
+
}
|
|
741
|
+
let vendor: string | undefined;
|
|
742
|
+
let architecture: string | undefined;
|
|
743
|
+
try {
|
|
744
|
+
const info = adapter.info ?? (await adapter.requestAdapterInfo?.());
|
|
745
|
+
vendor = info?.vendor || undefined;
|
|
746
|
+
architecture = info?.architecture || undefined;
|
|
747
|
+
} catch {
|
|
748
|
+
// adapter info is best-effort
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
available: true,
|
|
752
|
+
adapterAvailable: true,
|
|
753
|
+
shaderF16Supported: adapter.features.has('shader-f16'),
|
|
754
|
+
maxBufferSizeMB: bytesToMB(adapter.limits?.maxBufferSize),
|
|
755
|
+
maxStorageBufferBindingSizeMB: bytesToMB(adapter.limits?.maxStorageBufferBindingSize),
|
|
756
|
+
vendor,
|
|
757
|
+
architecture,
|
|
758
|
+
};
|
|
759
|
+
} catch {
|
|
760
|
+
return { available: true, adapterAvailable: false };
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
/** Map an MLC/WebLLM engine-creation error message to a coarse reason. */
|
|
765
|
+
const classifyEngineError = (message: string): LocalSlowLaneLoadErrorReason => {
|
|
766
|
+
const lower = message.toLowerCase();
|
|
767
|
+
if (
|
|
768
|
+
lower.includes('loading chunk') ||
|
|
769
|
+
lower.includes('dynamically imported module') ||
|
|
770
|
+
lower.includes('dynamic import')
|
|
771
|
+
) {
|
|
772
|
+
return 'module_load_failed';
|
|
773
|
+
}
|
|
774
|
+
if (
|
|
775
|
+
lower.includes('out of memory') ||
|
|
776
|
+
// eslint-disable-next-line require-unicode-regexp
|
|
777
|
+
/\boom\b/.test(lower) ||
|
|
778
|
+
lower.includes('allocation') ||
|
|
779
|
+
lower.includes('exceeds') ||
|
|
780
|
+
lower.includes('buffer size') ||
|
|
781
|
+
lower.includes('not enough memory')
|
|
782
|
+
) {
|
|
783
|
+
return 'insufficient_memory';
|
|
784
|
+
}
|
|
785
|
+
// Pre-flight already returns missing_shader_f16 when the feature is absent,
|
|
786
|
+
// so only match the exact feature token here — not bare 'shader' (compile
|
|
787
|
+
// errors) or bare 'f16' (present in model ids like q0f16-MLC).
|
|
788
|
+
if (lower.includes('shader-f16') || lower.includes('shader_f16')) {
|
|
789
|
+
return 'missing_shader_f16';
|
|
790
|
+
}
|
|
791
|
+
if (
|
|
792
|
+
lower.includes('fetch') ||
|
|
793
|
+
lower.includes('network') ||
|
|
794
|
+
lower.includes('download') ||
|
|
795
|
+
lower.includes('http') ||
|
|
796
|
+
lower.includes('cache')
|
|
797
|
+
) {
|
|
798
|
+
return 'model_download_failed';
|
|
799
|
+
}
|
|
800
|
+
return 'init_failed';
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// Canonical, controlled failure descriptions. We never emit the raw engine
|
|
804
|
+
// error into analytics — it can embed customer-context URLs/paths (HOT-120175)
|
|
805
|
+
// — so the analytics `message` is always one of these fixed strings.
|
|
806
|
+
const LOAD_FAILURE_MESSAGE: Record<LocalSlowLaneLoadErrorReason, string> = {
|
|
807
|
+
webgpu_unavailable: 'WebGPU is not available in this browser',
|
|
808
|
+
webgpu_no_adapter: 'No compatible WebGPU adapter found',
|
|
809
|
+
missing_shader_f16: 'GPU does not support the shader-f16 feature',
|
|
810
|
+
insufficient_memory: 'Insufficient GPU memory to load the model',
|
|
811
|
+
model_download_failed: 'Failed to download model assets',
|
|
812
|
+
module_load_failed: 'Failed to load the web-llm runtime module',
|
|
813
|
+
init_failed: 'Model engine failed to initialise',
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const handleLoadFailure = (
|
|
817
|
+
reason: LocalSlowLaneLoadErrorReason,
|
|
818
|
+
capabilities: WebGpuCapabilities,
|
|
819
|
+
// Raw engine error — local debug logging only, never sent to analytics.
|
|
820
|
+
debugDetail?: string,
|
|
821
|
+
): void => {
|
|
822
|
+
ready = false;
|
|
823
|
+
const message = LOAD_FAILURE_MESSAGE[reason];
|
|
824
|
+
if (isAutocompleteDebugEnabled()) {
|
|
825
|
+
// eslint-disable-next-line no-console
|
|
826
|
+
console.log(
|
|
827
|
+
`[LocalSlowLane] Engine initialisation failed (${reason}): ${debugDetail ?? message}`,
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
onStatus?.(`Engine initialisation failed: ${message}`);
|
|
831
|
+
onLoadError?.({
|
|
832
|
+
reason,
|
|
833
|
+
message,
|
|
834
|
+
modelId,
|
|
835
|
+
embeddingModelId: LOCAL_MLC_EMBEDDING_MODEL_ID,
|
|
836
|
+
capabilities,
|
|
837
|
+
});
|
|
838
|
+
engineInitPromise = null;
|
|
839
|
+
initFailed = true;
|
|
840
|
+
};
|
|
841
|
+
|
|
644
842
|
const initEngine = async (): Promise<void> => {
|
|
843
|
+
const capabilities = await probeWebGpuCapabilities();
|
|
844
|
+
|
|
845
|
+
// ── Pre-flight: machine limitations short-circuit before the expensive load ──
|
|
846
|
+
if (!capabilities.available) {
|
|
847
|
+
handleLoadFailure('webgpu_unavailable', capabilities);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (capabilities.adapterAvailable === false) {
|
|
851
|
+
handleLoadFailure('webgpu_no_adapter', capabilities);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (capabilities.shaderF16Supported === false) {
|
|
855
|
+
handleLoadFailure('missing_shader_f16', capabilities);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const startTime = performance.now();
|
|
860
|
+
|
|
645
861
|
try {
|
|
646
862
|
if (isAutocompleteDebugEnabled()) {
|
|
647
863
|
// eslint-disable-next-line no-console
|
|
@@ -653,10 +869,6 @@ export const createLocalSlowLaneClient = (
|
|
|
653
869
|
}
|
|
654
870
|
onStatus?.(`Initialising models: ${modelId} + ${LOCAL_MLC_EMBEDDING_MODEL_ID}…`);
|
|
655
871
|
|
|
656
|
-
if (!('gpu' in navigator)) {
|
|
657
|
-
throw new Error('WebGPU not supported');
|
|
658
|
-
}
|
|
659
|
-
|
|
660
872
|
// Fetch the web-llm runtime and the BE-parity lookup tables in parallel;
|
|
661
873
|
// both are dynamically imported so they stay out of the main editor chunk.
|
|
662
874
|
const [{ MLCEngine: MLCEngineCtor, prebuiltAppConfig }] = await Promise.all([
|
|
@@ -714,6 +926,7 @@ export const createLocalSlowLaneClient = (
|
|
|
714
926
|
|
|
715
927
|
engine = newEngine;
|
|
716
928
|
ready = true;
|
|
929
|
+
const loadDurationMs = Math.round(performance.now() - startTime);
|
|
717
930
|
|
|
718
931
|
if (isAutocompleteDebugEnabled()) {
|
|
719
932
|
// eslint-disable-next-line no-console
|
|
@@ -740,16 +953,15 @@ export const createLocalSlowLaneClient = (
|
|
|
740
953
|
);
|
|
741
954
|
}
|
|
742
955
|
onStatus?.('Model loaded and ready.');
|
|
956
|
+
onLoadSuccess?.({
|
|
957
|
+
modelId,
|
|
958
|
+
embeddingModelId: LOCAL_MLC_EMBEDDING_MODEL_ID,
|
|
959
|
+
loadDurationMs,
|
|
960
|
+
capabilities,
|
|
961
|
+
});
|
|
743
962
|
} catch (err) {
|
|
744
963
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
745
|
-
|
|
746
|
-
if (isAutocompleteDebugEnabled()) {
|
|
747
|
-
// eslint-disable-next-line no-console
|
|
748
|
-
console.log(`[LocalSlowLane] Engine initialisation failed: ${errorMsg}`);
|
|
749
|
-
}
|
|
750
|
-
onStatus?.(`Engine initialisation failed: ${errorMsg}`);
|
|
751
|
-
engineInitPromise = null;
|
|
752
|
-
initFailed = true;
|
|
964
|
+
handleLoadFailure(classifyEngineError(errorMsg), capabilities, errorMsg);
|
|
753
965
|
}
|
|
754
966
|
};
|
|
755
967
|
|
|
@@ -747,6 +747,7 @@ const unwrapJsonModule = <T>(mod: unknown, shape: 'object' | 'array'): T | null
|
|
|
747
747
|
|
|
748
748
|
export const loadVectorsAsync = async (options?: {
|
|
749
749
|
getBinaryUrl?: () => Promise<string>;
|
|
750
|
+
isLocalLLM?: boolean;
|
|
750
751
|
}): Promise<void> => {
|
|
751
752
|
if (vectorStore || vectorsLoadStarted) {
|
|
752
753
|
return;
|
|
@@ -758,15 +759,16 @@ export const loadVectorsAsync = async (options?: {
|
|
|
758
759
|
);
|
|
759
760
|
return;
|
|
760
761
|
}
|
|
762
|
+
const isLocalLLM = options?.isLocalLLM ?? false;
|
|
761
763
|
vectorsLoadStarted = true;
|
|
762
|
-
startExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton');
|
|
764
|
+
startExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', { isLocalLLM });
|
|
763
765
|
|
|
764
766
|
let url: string;
|
|
765
767
|
try {
|
|
766
768
|
url = await options.getBinaryUrl();
|
|
767
769
|
} catch (e) {
|
|
768
770
|
vectorsLoadStarted = false;
|
|
769
|
-
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', { errorType: 'resolve_url' });
|
|
771
|
+
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', { isLocalLLM, errorType: 'resolve_url' });
|
|
770
772
|
// eslint-disable-next-line no-console
|
|
771
773
|
console.warn('[text-predictor] Failed to resolve vectors URL:', e);
|
|
772
774
|
return;
|
|
@@ -777,6 +779,7 @@ export const loadVectorsAsync = async (options?: {
|
|
|
777
779
|
if (!res.ok) {
|
|
778
780
|
vectorsLoadStarted = false;
|
|
779
781
|
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
|
|
782
|
+
isLocalLLM,
|
|
780
783
|
status: res.status,
|
|
781
784
|
errorType: 'http_error',
|
|
782
785
|
});
|
|
@@ -809,6 +812,7 @@ export const loadVectorsAsync = async (options?: {
|
|
|
809
812
|
|
|
810
813
|
vectorStore = { float32, wordIndex, dim };
|
|
811
814
|
succeedExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
|
|
815
|
+
isLocalLLM,
|
|
812
816
|
wordCount: nWords,
|
|
813
817
|
dim,
|
|
814
818
|
sizeBytes: float32.byteLength,
|
|
@@ -823,7 +827,7 @@ export const loadVectorsAsync = async (options?: {
|
|
|
823
827
|
}
|
|
824
828
|
} catch (e) {
|
|
825
829
|
vectorsLoadStarted = false;
|
|
826
|
-
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', { errorType: 'network' });
|
|
830
|
+
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', { isLocalLLM, errorType: 'network' });
|
|
827
831
|
// eslint-disable-next-line no-console
|
|
828
832
|
console.warn('[text-predictor] Failed to load vectors:', e);
|
|
829
833
|
}
|
|
@@ -835,7 +839,7 @@ export const initVectors = (store: VectorStore): void => {
|
|
|
835
839
|
|
|
836
840
|
let vocabularyLoadPromise: Promise<void> | undefined;
|
|
837
841
|
|
|
838
|
-
export const loadDefaultVocabulary = (): Promise<void> => {
|
|
842
|
+
export const loadDefaultVocabulary = (options?: { isLocalLLM?: boolean }): Promise<void> => {
|
|
839
843
|
if (isInitialized) {
|
|
840
844
|
return Promise.resolve();
|
|
841
845
|
}
|
|
@@ -843,8 +847,9 @@ export const loadDefaultVocabulary = (): Promise<void> => {
|
|
|
843
847
|
return vocabularyLoadPromise;
|
|
844
848
|
}
|
|
845
849
|
|
|
850
|
+
const isLocalLLM = options?.isLocalLLM ?? false;
|
|
846
851
|
vocabularyLoadPromise = (async () => {
|
|
847
|
-
startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
|
|
852
|
+
startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', { isLocalLLM });
|
|
848
853
|
|
|
849
854
|
try {
|
|
850
855
|
// The L2 vocabulary and L3 word list are code-split into their own async
|
|
@@ -879,11 +884,12 @@ export const loadDefaultVocabulary = (): Promise<void> => {
|
|
|
879
884
|
initVocabulary({ terms });
|
|
880
885
|
|
|
881
886
|
succeedExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
887
|
+
isLocalLLM,
|
|
882
888
|
l2WordCount: terms.length,
|
|
883
889
|
l3WordCount: l3VocabularyData.length,
|
|
884
890
|
});
|
|
885
891
|
} catch (e) {
|
|
886
|
-
failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', { errorType: 'parse_error' });
|
|
892
|
+
failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', { isLocalLLM, errorType: 'parse_error' });
|
|
887
893
|
// Allow a later call to retry the load rather than caching the failure.
|
|
888
894
|
vocabularyLoadPromise = undefined;
|
|
889
895
|
throw e;
|