@atlaskit/editor-plugin-autocomplete 6.0.0 → 7.0.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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/analytics/ufo.js +3 -2
- package/dist/cjs/pm-plugins/artifacts-manifest.js +210 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +10 -2
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +68 -153
- package/dist/cjs/pm-plugins/scoring-pipeline.js +130 -28
- package/dist/cjs/pm-plugins/text-predictor.js +45 -106
- package/dist/es2019/analytics/ufo.js +4 -2
- package/dist/es2019/pm-plugins/artifacts-manifest.js +126 -0
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +10 -2
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +8 -78
- package/dist/es2019/pm-plugins/scoring-pipeline.js +97 -21
- package/dist/es2019/pm-plugins/text-predictor.js +17 -59
- package/dist/esm/analytics/ufo.js +3 -2
- package/dist/esm/pm-plugins/artifacts-manifest.js +204 -0
- package/dist/esm/pm-plugins/autocomplete-plugin.js +10 -2
- package/dist/esm/pm-plugins/local-slow-lane-client.js +67 -148
- package/dist/esm/pm-plugins/scoring-pipeline.js +128 -26
- package/dist/esm/pm-plugins/text-predictor.js +45 -98
- package/dist/types/analytics/ufo.d.ts +1 -0
- package/dist/types/pm-plugins/artifacts-manifest.d.ts +55 -0
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +5 -11
- package/dist/types/pm-plugins/scoring-pipeline.d.ts +14 -0
- package/dist/types/pm-plugins/text-predictor.d.ts +0 -1
- package/package.json +2 -5
- package/src/analytics/ufo.ts +5 -0
- package/src/pm-plugins/artifacts-manifest.ts +151 -0
- package/src/pm-plugins/autocomplete-plugin.ts +14 -12
- package/src/pm-plugins/local-slow-lane-client.ts +10 -92
- package/src/pm-plugins/scoring-pipeline.ts +115 -23
- package/src/pm-plugins/text-predictor.ts +26 -76
- package/dist/cjs/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
- package/dist/cjs/pm-plugins/data/first_token_to_words.json +0 -1
- package/dist/cjs/pm-plugins/data/ghost_pos_tags.json +0 -43
- package/dist/cjs/pm-plugins/data/grammar_transitions_10k.json +0 -46
- package/dist/cjs/pm-plugins/data/l3_vocabulary.json +0 -20002
- package/dist/cjs/pm-plugins/data/vocabulary_10k.json +0 -38794
- package/dist/cjs/pm-plugins/data/word_index_10k.json +0 -7762
- package/dist/es2019/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
- package/dist/es2019/pm-plugins/data/first_token_to_words.json +0 -1
- package/dist/es2019/pm-plugins/data/ghost_pos_tags.json +0 -43
- package/dist/es2019/pm-plugins/data/grammar_transitions_10k.json +0 -46
- package/dist/es2019/pm-plugins/data/l3_vocabulary.json +0 -20002
- package/dist/es2019/pm-plugins/data/vocabulary_10k.json +0 -38794
- package/dist/es2019/pm-plugins/data/word_index_10k.json +0 -7762
- package/dist/esm/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
- package/dist/esm/pm-plugins/data/first_token_to_words.json +0 -1
- package/dist/esm/pm-plugins/data/ghost_pos_tags.json +0 -43
- package/dist/esm/pm-plugins/data/grammar_transitions_10k.json +0 -46
- package/dist/esm/pm-plugins/data/l3_vocabulary.json +0 -20002
- package/dist/esm/pm-plugins/data/vocabulary_10k.json +0 -38794
- package/dist/esm/pm-plugins/data/word_index_10k.json +0 -7762
- package/src/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
- package/src/pm-plugins/data/first_token_to_words.json +0 -1
- package/src/pm-plugins/data/ghost_pos_tags.json +0 -43
- package/src/pm-plugins/data/grammar_transitions_10k.json +0 -46
- package/src/pm-plugins/data/l3_vocabulary.json +0 -20002
- package/src/pm-plugins/data/vocabulary_10k.json +0 -38794
- package/src/pm-plugins/data/word-vectors_10k.bin +0 -3
- package/src/pm-plugins/data/word_index_10k.json +0 -7762
|
@@ -39,6 +39,7 @@ import type { MLCEngine, InitProgressReport, AppConfig, LogitProcessor } from '@
|
|
|
39
39
|
|
|
40
40
|
import { abortExp, EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
41
41
|
|
|
42
|
+
import { ARTIFACT_NAME, fetchArtifactJson } from './artifacts-manifest';
|
|
42
43
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
43
44
|
import { isWordBoundary } from './slow-lane-client';
|
|
44
45
|
|
|
@@ -349,78 +350,9 @@ let prefixMapTokenIds: number[] = [];
|
|
|
349
350
|
let bePayloadDataPromise: Promise<void> | undefined;
|
|
350
351
|
|
|
351
352
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* 1. **`.default`-wrapped namespace** — classic webpack (and Jest) hang the
|
|
356
|
-
* JSON value under the `default` export.
|
|
357
|
-
* 2. **Named-exports namespace** — webpack 5 / atlaspack with JSON
|
|
358
|
-
* named-exports (or native ESM JSON modules) expose each top-level key as
|
|
359
|
-
* a named export and shadow `default`, so `mod.default` can be `undefined`
|
|
360
|
-
* (or some unrelated value) even though `mod` itself holds the data.
|
|
361
|
-
*
|
|
362
|
-
* The caller MUST declare the underlying JSON shape via `shape` because, in
|
|
363
|
-
* named-exports mode, a dense array `["a","b"]` and a sparse numeric-keyed
|
|
364
|
-
* object `{"5":"a","12":"b"}` are emitted identically (`{"0":..}` / `{"5":..}`);
|
|
365
|
-
* no runtime heuristic can tell them apart, so only the caller knows which:
|
|
366
|
-
*
|
|
367
|
-
* - `'object'` — the JSON is a `{...}` (including sparse maps keyed by integer
|
|
368
|
-
* IDs). The named exports are rebuilt into a plain object so `Object.entries`
|
|
369
|
-
* yields the real keys, not synthetic array indices.
|
|
370
|
-
* - `'array'` — the JSON is a `[...]`, reconstructed from the `0..n-1` indices.
|
|
371
|
-
*
|
|
372
|
-
* :param mod: The raw module object returned by `await import('./*.json')`.
|
|
373
|
-
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
374
|
-
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
375
|
-
*/
|
|
376
|
-
function unwrapJsonModule<T>(mod: unknown, shape: 'object' | 'array'): T | null {
|
|
377
|
-
if (mod == null || typeof mod !== 'object') {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
const namespace = mod as Record<string, unknown> & { default?: unknown };
|
|
381
|
-
|
|
382
|
-
// Compute the named-export own-keys (strip synthetic markers).
|
|
383
|
-
const ownKeys = Object.keys(namespace).filter((k) => k !== 'default' && k !== '__esModule');
|
|
384
|
-
|
|
385
|
-
// PREFER named exports when present — they always reflect the JSON's real
|
|
386
|
-
// top-level keys / indices, regardless of what `default` happens to be.
|
|
387
|
-
// Under JSON named-exports mode `default` is not necessarily the parsed
|
|
388
|
-
// value (e.g. for `{"service": 0, ...}` it can be the number `0`, with the
|
|
389
|
-
// real data in the named exports), so taking `default` first would corrupt it.
|
|
390
|
-
if (ownKeys.length > 0) {
|
|
391
|
-
if (shape === 'array') {
|
|
392
|
-
// JSON arrays are dense; reconstruct from `0..length-1` indices.
|
|
393
|
-
const len = ownKeys.length;
|
|
394
|
-
const arr = new Array(len);
|
|
395
|
-
for (let i = 0; i < len; i++) {
|
|
396
|
-
arr[i] = namespace[String(i)];
|
|
397
|
-
}
|
|
398
|
-
return arr as T;
|
|
399
|
-
}
|
|
400
|
-
// shape === 'object'. Rebuild a plain object from the (stripped) own
|
|
401
|
-
// keys so callers can `Object.entries()` it without iterating over
|
|
402
|
-
// `default` / `__esModule`, and to detach from the module-namespace
|
|
403
|
-
// object (which is sealed/non-extensible on some bundler outputs).
|
|
404
|
-
const obj: Record<string, unknown> = {};
|
|
405
|
-
for (const k of ownKeys) {
|
|
406
|
-
obj[k] = namespace[k];
|
|
407
|
-
}
|
|
408
|
-
return obj as T;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Fallback: no named exports — classic webpack JSON-module interop where
|
|
412
|
-
// the whole parsed JSON value is hung under `default`. Trust it.
|
|
413
|
-
if ('default' in namespace && namespace.default != null) {
|
|
414
|
-
return namespace.default as T;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
422
|
-
* The dynamic imports are split into their own async chunks so neither file is
|
|
423
|
-
* bundled into the editor's main chunk unless local inference is initialised.
|
|
353
|
+
* Lazily load and build the BE-parity lookup tables from their CDN payloads.
|
|
354
|
+
* Both artifacts are fetched on demand so neither is downloaded unless local
|
|
355
|
+
* inference is initialised.
|
|
424
356
|
*
|
|
425
357
|
* :returns:
|
|
426
358
|
* A promise that resolves once `firstTokenToWords`, `l2Words` and
|
|
@@ -429,31 +361,17 @@ function unwrapJsonModule<T>(mod: unknown, shape: 'object' | 'array'): T | null
|
|
|
429
361
|
const loadBePayloadData = (): Promise<void> => {
|
|
430
362
|
if (!bePayloadDataPromise) {
|
|
431
363
|
bePayloadDataPromise = (async () => {
|
|
432
|
-
const [
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
),
|
|
436
|
-
import(
|
|
437
|
-
/* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */ './data/vocabulary_10k.json'
|
|
438
|
-
),
|
|
364
|
+
const [firstTokenToWordsData, vocabularyData] = await Promise.all([
|
|
365
|
+
fetchArtifactJson<Record<string, string[]>>(ARTIFACT_NAME.FIRST_TOKEN_TO_WORDS),
|
|
366
|
+
fetchArtifactJson<{ words: Record<string, unknown> }>(ARTIFACT_NAME.VOCABULARY),
|
|
439
367
|
]);
|
|
440
368
|
|
|
441
|
-
const firstTokenToWordsData = unwrapJsonModule<Record<string, string[]>>(
|
|
442
|
-
firstTokenToWordsModule,
|
|
443
|
-
'object',
|
|
444
|
-
);
|
|
445
|
-
const vocabularyData = unwrapJsonModule<{ words: Record<string, unknown> }>(
|
|
446
|
-
vocabularyModule,
|
|
447
|
-
'object',
|
|
448
|
-
);
|
|
449
|
-
|
|
450
369
|
if (firstTokenToWordsData == null || vocabularyData?.words == null) {
|
|
451
370
|
// Hard-fail with a precise message so the catch() in initEngine logs
|
|
452
|
-
// exactly which
|
|
453
|
-
//
|
|
454
|
-
// helper was added.
|
|
371
|
+
// exactly which payload was malformed, rather than the generic V8
|
|
372
|
+
// "Cannot convert undefined or null to object".
|
|
455
373
|
throw new Error(
|
|
456
|
-
`[LocalSlowLane]
|
|
374
|
+
`[LocalSlowLane] unexpected artifact payload — ` +
|
|
457
375
|
`firstTokenToWordsData=${firstTokenToWordsData == null ? 'null/undefined' : 'defined'}, ` +
|
|
458
376
|
`vocabularyData=${vocabularyData == null ? 'null/undefined' : vocabularyData.words == null ? 'defined but missing .words' : 'defined'}`,
|
|
459
377
|
);
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Operates synchronously on pre-loaded data. Each stage gracefully degrades
|
|
5
5
|
* when its required data isn't available (cold → warm → full warm).
|
|
6
|
+
*
|
|
7
|
+
* The grammar/POS payloads are fetched from the CDN by loadGrammarDataAsync, so
|
|
8
|
+
* the grammar filter is a pass-through until that resolves.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
+
import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
12
|
+
|
|
13
|
+
import { ARTIFACT_NAME, fetchArtifactJson } from './artifacts-manifest';
|
|
14
|
+
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
11
15
|
|
|
12
16
|
// ─── Types ──────────────────────────────────────────────────
|
|
13
17
|
|
|
@@ -59,22 +63,114 @@ const L1_SESSION_CAP = 1.2;
|
|
|
59
63
|
// at 0.00024) from triggering re-ranking.
|
|
60
64
|
const LM_GATE_THRESHOLD = 0.0005;
|
|
61
65
|
|
|
62
|
-
// ─── Grammar Data (
|
|
66
|
+
// ─── Grammar Data (fetched once from the CDN) ────────────────
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
Object.entries(posTagsData as Record<string, string[]>),
|
|
66
|
-
);
|
|
68
|
+
let posTags: Map<string, string[]> = new Map();
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* GHOST POS DICTIONARY
|
|
72
|
+
* A mapping of common structural English words that were stripped from the main
|
|
73
|
+
* domain vocabulary. This allows the grammar filter to understand context
|
|
74
|
+
* without suggesting these words to the user.
|
|
75
|
+
*/
|
|
76
|
+
let ghostPosTags: Record<string, string[]> = {};
|
|
69
77
|
|
|
70
78
|
/**
|
|
71
|
-
* Precomputed map from each POS tag to the set of allowed next POS tags
|
|
72
|
-
*
|
|
73
|
-
* never re-iterates the transition rules per call.
|
|
79
|
+
* Precomputed map from each POS tag to the set of allowed next POS tags, so
|
|
80
|
+
* applyGrammarFilter never re-iterates the transition rules per call.
|
|
74
81
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
let precomputedAllowedByPos: Map<string, Set<string>> = new Map();
|
|
83
|
+
|
|
84
|
+
let grammarDataPromise: Promise<void> | undefined;
|
|
85
|
+
|
|
86
|
+
const setGrammarData = (data: {
|
|
87
|
+
ghostPosTags: Record<string, string[]>;
|
|
88
|
+
grammarTransitions: GrammarTransitions;
|
|
89
|
+
posTags: Record<string, string[]>;
|
|
90
|
+
}): void => {
|
|
91
|
+
posTags = new Map(Object.entries(data.posTags));
|
|
92
|
+
ghostPosTags = data.ghostPosTags;
|
|
93
|
+
precomputedAllowedByPos = new Map(
|
|
94
|
+
Object.entries(data.grammarTransitions.transitions ?? {}).map(([pos, rule]) => [
|
|
95
|
+
pos,
|
|
96
|
+
new Set(rule.allowed),
|
|
97
|
+
]),
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const isGrammarDataLoaded = (): boolean => precomputedAllowedByPos.size > 0;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fetch the POS tag and grammar transition payloads from the CDN.
|
|
105
|
+
*
|
|
106
|
+
* Safe to call repeatedly: the in-flight promise is shared, and a failure is not
|
|
107
|
+
* cached so a later call can retry.
|
|
108
|
+
*/
|
|
109
|
+
export const loadGrammarDataAsync = (options?: {
|
|
110
|
+
isLocalLLM?: boolean;
|
|
111
|
+
surface?: string;
|
|
112
|
+
}): Promise<void> => {
|
|
113
|
+
if (grammarDataPromise) {
|
|
114
|
+
return grammarDataPromise;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const isLocalLLM = options?.isLocalLLM ?? false;
|
|
118
|
+
const surface = options?.surface;
|
|
119
|
+
|
|
120
|
+
grammarDataPromise = (async () => {
|
|
121
|
+
startExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
122
|
+
isLocalLLM,
|
|
123
|
+
...(surface ? { surface } : {}),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const [posTagsData, ghostPosTagsData, grammarTransitionsData] = await Promise.all([
|
|
128
|
+
fetchArtifactJson<Record<string, string[]>>(ARTIFACT_NAME.POS_TAGS),
|
|
129
|
+
fetchArtifactJson<Record<string, string[]>>(ARTIFACT_NAME.GHOST_POS_TAGS),
|
|
130
|
+
fetchArtifactJson<GrammarTransitions>(ARTIFACT_NAME.GRAMMAR_TRANSITIONS),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
if (grammarTransitionsData?.transitions == null) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`[scoring-pipeline] ${ARTIFACT_NAME.GRAMMAR_TRANSITIONS} is missing its \`transitions\` wrapper`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setGrammarData({
|
|
140
|
+
posTags: posTagsData ?? {},
|
|
141
|
+
ghostPosTags: ghostPosTagsData ?? {},
|
|
142
|
+
grammarTransitions: grammarTransitionsData,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
succeedExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
146
|
+
isLocalLLM,
|
|
147
|
+
posTagCount: posTags.size,
|
|
148
|
+
transitionCount: precomputedAllowedByPos.size,
|
|
149
|
+
...(surface ? { surface } : {}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (isAutocompleteDebugEnabled()) {
|
|
153
|
+
// eslint-disable-next-line no-console
|
|
154
|
+
console.log('[scoring-pipeline] Grammar data loaded:', {
|
|
155
|
+
posTagCount: posTags.size,
|
|
156
|
+
ghostPosTagCount: Object.keys(ghostPosTags).length,
|
|
157
|
+
transitionCount: precomputedAllowedByPos.size,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
failExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
162
|
+
isLocalLLM,
|
|
163
|
+
errorType: 'network',
|
|
164
|
+
...(surface ? { surface } : {}),
|
|
165
|
+
});
|
|
166
|
+
// Allow a later call to retry the load rather than caching the failure.
|
|
167
|
+
grammarDataPromise = undefined;
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
|
|
172
|
+
return grammarDataPromise;
|
|
173
|
+
};
|
|
78
174
|
|
|
79
175
|
// ─── Math ───────────────────────────────────────────────────
|
|
80
176
|
|
|
@@ -135,14 +231,6 @@ function scoreStage1(
|
|
|
135
231
|
|
|
136
232
|
// ─── Grammar Filter ─────────────────────────────────────────
|
|
137
233
|
|
|
138
|
-
/**
|
|
139
|
-
* GHOST POS DICTIONARY
|
|
140
|
-
* A hardcoded mapping of common structural English words that were stripped
|
|
141
|
-
* from the main domain vocabulary. This allows the grammar filter to understand
|
|
142
|
-
* context without suggesting these words to the user.
|
|
143
|
-
*/
|
|
144
|
-
const ghostPosTags: Record<string, string[]> = ghostPosTagsData as Record<string, string[]>;
|
|
145
|
-
|
|
146
234
|
type FilterEntry = {
|
|
147
235
|
candidate: ScoringCandidate;
|
|
148
236
|
freqScore: number;
|
|
@@ -154,7 +242,11 @@ function applyGrammarFilter(
|
|
|
154
242
|
candidates: FilterEntry[],
|
|
155
243
|
previousWord: string,
|
|
156
244
|
): { filtered: FilterEntry[]; grammarMeta: GrammarFilterMeta | null } {
|
|
157
|
-
|
|
245
|
+
// Pass everything through until loadGrammarDataAsync resolves — filtering on a
|
|
246
|
+
// partially loaded ruleset would drop valid candidates.
|
|
247
|
+
if (!previousWord || !isGrammarDataLoaded()) {
|
|
248
|
+
return { filtered: candidates, grammarMeta: null };
|
|
249
|
+
}
|
|
158
250
|
|
|
159
251
|
const lowerPrev = previousWord.toLowerCase();
|
|
160
252
|
const prevTags = ghostPosTags[lowerPrev] || posTags.get(lowerPrev);
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
19
19
|
|
|
20
|
-
// The vocabulary, L3 and word-index
|
|
21
|
-
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents
|
|
22
|
-
// the editor's
|
|
20
|
+
// The vocabulary, L3 and word-index payloads are fetched from the CDN in
|
|
21
|
+
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents never enter
|
|
22
|
+
// the editor's bundle and only load when autocomplete is initialised.
|
|
23
|
+
import { ARTIFACT_NAME, fetchArtifactJson, getArtifactUrl } from './artifacts-manifest';
|
|
23
24
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
24
25
|
import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
|
|
25
26
|
import type { ScoringCandidate } from './scoring-pipeline';
|
|
@@ -709,60 +710,13 @@ interface VocabularyJson {
|
|
|
709
710
|
>;
|
|
710
711
|
}
|
|
711
712
|
|
|
712
|
-
/**
|
|
713
|
-
* Unwrap a dynamically imported JSON module to its parsed value, handling both
|
|
714
|
-
* interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
|
|
715
|
-
* (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
|
|
716
|
-
* modules, where `default` can be a misleading scalar). Named exports are
|
|
717
|
-
* preferred when present. The caller declares the JSON `shape` because a dense
|
|
718
|
-
* array and a sparse numeric-keyed object are emitted identically as named
|
|
719
|
-
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
720
|
-
*/
|
|
721
|
-
function unwrapJsonModule<T>(mod: unknown, shape: 'object' | 'array'): T | null {
|
|
722
|
-
if (mod == null || typeof mod !== 'object') {
|
|
723
|
-
return null;
|
|
724
|
-
}
|
|
725
|
-
const namespace = mod as Record<string, unknown> & { default?: unknown };
|
|
726
|
-
const ownKeys = Object.keys(namespace).filter((k) => k !== 'default' && k !== '__esModule');
|
|
727
|
-
|
|
728
|
-
if (ownKeys.length > 0) {
|
|
729
|
-
if (shape === 'array') {
|
|
730
|
-
const len = ownKeys.length;
|
|
731
|
-
const arr = new Array(len);
|
|
732
|
-
for (let i = 0; i < len; i++) {
|
|
733
|
-
arr[i] = namespace[String(i)];
|
|
734
|
-
}
|
|
735
|
-
return arr as T;
|
|
736
|
-
}
|
|
737
|
-
const obj: Record<string, unknown> = {};
|
|
738
|
-
for (const k of ownKeys) {
|
|
739
|
-
obj[k] = namespace[k];
|
|
740
|
-
}
|
|
741
|
-
return obj as T;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if ('default' in namespace && namespace.default != null) {
|
|
745
|
-
return namespace.default as T;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
return null;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
713
|
export const loadVectorsAsync = async (options?: {
|
|
752
|
-
getBinaryUrl?: () => Promise<string>;
|
|
753
714
|
isLocalLLM?: boolean;
|
|
754
715
|
surface?: string;
|
|
755
716
|
}): Promise<void> => {
|
|
756
717
|
if (vectorStore || vectorsLoadStarted) {
|
|
757
718
|
return;
|
|
758
719
|
}
|
|
759
|
-
if (!options?.getBinaryUrl) {
|
|
760
|
-
// eslint-disable-next-line no-console
|
|
761
|
-
console.warn(
|
|
762
|
-
'[text-predictor] loadVectorsAsync called without a getBinaryUrl — vectors will not load. Pass getVectorsBinaryUrl via plugin options.',
|
|
763
|
-
);
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
720
|
const isLocalLLM = options?.isLocalLLM ?? false;
|
|
767
721
|
const surface = options?.surface;
|
|
768
722
|
vectorsLoadStarted = true;
|
|
@@ -773,7 +727,7 @@ export const loadVectorsAsync = async (options?: {
|
|
|
773
727
|
|
|
774
728
|
let url: string;
|
|
775
729
|
try {
|
|
776
|
-
url = await
|
|
730
|
+
url = await getArtifactUrl(ARTIFACT_NAME.WORD_VECTORS);
|
|
777
731
|
} catch (e) {
|
|
778
732
|
vectorsLoadStarted = false;
|
|
779
733
|
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
|
|
@@ -803,22 +757,23 @@ export const loadVectorsAsync = async (options?: {
|
|
|
803
757
|
const buffer = await res.arrayBuffer();
|
|
804
758
|
const float32 = new Float32Array(buffer);
|
|
805
759
|
|
|
806
|
-
//
|
|
807
|
-
// (e.g. the word "default"
|
|
808
|
-
//
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
wordIndexModule,
|
|
814
|
-
'object',
|
|
760
|
+
// The word index is published either bare or wrapped as `{ "index": {…} }`
|
|
761
|
+
// (the wrapper exists so no real entry, e.g. the word "default", can shadow
|
|
762
|
+
// a bundler-synthesised ESM export). Detect the wrapper by value type, not
|
|
763
|
+
// by key presence — "index" is itself a plausible vocabulary word, and in a
|
|
764
|
+
// bare index every value is a number.
|
|
765
|
+
const wordIndexPayload = await fetchArtifactJson<Record<string, unknown>>(
|
|
766
|
+
ARTIFACT_NAME.WORD_INDEX,
|
|
815
767
|
);
|
|
816
|
-
const
|
|
768
|
+
const wrappedIndex = wordIndexPayload?.index;
|
|
769
|
+
const wordIndex = (typeof wrappedIndex === 'object' && wrappedIndex !== null
|
|
770
|
+
? wrappedIndex
|
|
771
|
+
: (wordIndexPayload ?? {})) as Record<string, number>;
|
|
817
772
|
const nWords = Object.keys(wordIndex).length;
|
|
818
773
|
if (nWords === 0) {
|
|
819
774
|
// eslint-disable-next-line no-console
|
|
820
775
|
console.warn(
|
|
821
|
-
|
|
776
|
+
`[text-predictor] ${ARTIFACT_NAME.WORD_INDEX} was empty — semantic scoring will be a no-op.`,
|
|
822
777
|
);
|
|
823
778
|
}
|
|
824
779
|
const dim = float32.length / nWords;
|
|
@@ -877,22 +832,17 @@ export const loadDefaultVocabulary = (options?: {
|
|
|
877
832
|
});
|
|
878
833
|
|
|
879
834
|
try {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
import(
|
|
884
|
-
/* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */ './data/vocabulary_10k.json'
|
|
885
|
-
),
|
|
886
|
-
import(
|
|
887
|
-
/* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-l3-vocabulary" */ './data/l3_vocabulary.json'
|
|
888
|
-
),
|
|
835
|
+
const [vocabularyData, l3VocabularyData] = await Promise.all([
|
|
836
|
+
fetchArtifactJson<VocabularyJson>(ARTIFACT_NAME.VOCABULARY),
|
|
837
|
+
fetchArtifactJson<string[]>(ARTIFACT_NAME.L3_VOCABULARY),
|
|
889
838
|
]);
|
|
890
839
|
|
|
891
|
-
const vocabularyData = unwrapJsonModule<VocabularyJson>(vocabularyModule, 'object');
|
|
892
|
-
const l3VocabularyData = unwrapJsonModule<string[]>(l3VocabularyModule, 'array');
|
|
893
|
-
|
|
894
840
|
if (vocabularyData?.words == null || !Array.isArray(l3VocabularyData)) {
|
|
895
|
-
throw new Error(
|
|
841
|
+
throw new Error(
|
|
842
|
+
`[text-predictor] unexpected vocabulary payload — ` +
|
|
843
|
+
`${ARTIFACT_NAME.VOCABULARY} ${vocabularyData?.words == null ? 'is missing its `words` wrapper' : 'is valid'}, ` +
|
|
844
|
+
`${ARTIFACT_NAME.L3_VOCABULARY} ${Array.isArray(l3VocabularyData) ? 'is valid' : 'is not an array'}`,
|
|
845
|
+
);
|
|
896
846
|
}
|
|
897
847
|
|
|
898
848
|
const terms = Object.entries(vocabularyData.words).map(([word, stats]) => ({
|
|
@@ -917,7 +867,7 @@ export const loadDefaultVocabulary = (options?: {
|
|
|
917
867
|
} catch (e) {
|
|
918
868
|
failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
919
869
|
isLocalLLM,
|
|
920
|
-
errorType: '
|
|
870
|
+
errorType: 'load_error',
|
|
921
871
|
...(surface ? { surface } : {}),
|
|
922
872
|
});
|
|
923
873
|
// Allow a later call to retry the load rather than caching the failure.
|