@atlaskit/editor-plugin-autocomplete 6.0.0 → 8.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 +20 -0
- package/compass.yml +2 -2
- 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 +3 -6
- 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 +28 -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
|
@@ -3,11 +3,14 @@
|
|
|
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
|
-
import
|
|
10
|
-
import
|
|
11
|
+
import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
12
|
+
import { ARTIFACT_NAME, fetchArtifactJson } from './artifacts-manifest';
|
|
13
|
+
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
11
14
|
|
|
12
15
|
// ─── Types ──────────────────────────────────────────────────
|
|
13
16
|
|
|
@@ -28,17 +31,93 @@ const L1_SESSION_CAP = 1.2;
|
|
|
28
31
|
// at 0.00024) from triggering re-ranking.
|
|
29
32
|
const LM_GATE_THRESHOLD = 0.0005;
|
|
30
33
|
|
|
31
|
-
// ─── Grammar Data (
|
|
34
|
+
// ─── Grammar Data (fetched once from the CDN) ────────────────
|
|
35
|
+
|
|
36
|
+
let posTags = new Map();
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
/**
|
|
39
|
+
* GHOST POS DICTIONARY
|
|
40
|
+
* A mapping of common structural English words that were stripped from the main
|
|
41
|
+
* domain vocabulary. This allows the grammar filter to understand context
|
|
42
|
+
* without suggesting these words to the user.
|
|
43
|
+
*/
|
|
44
|
+
let ghostPosTags = {};
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
|
-
* Precomputed map from each POS tag to the set of allowed next POS tags
|
|
38
|
-
*
|
|
39
|
-
* never re-iterates the transition rules per call.
|
|
47
|
+
* Precomputed map from each POS tag to the set of allowed next POS tags, so
|
|
48
|
+
* applyGrammarFilter never re-iterates the transition rules per call.
|
|
40
49
|
*/
|
|
41
|
-
|
|
50
|
+
let precomputedAllowedByPos = new Map();
|
|
51
|
+
let grammarDataPromise;
|
|
52
|
+
const setGrammarData = data => {
|
|
53
|
+
var _data$grammarTransiti;
|
|
54
|
+
posTags = new Map(Object.entries(data.posTags));
|
|
55
|
+
ghostPosTags = data.ghostPosTags;
|
|
56
|
+
precomputedAllowedByPos = new Map(Object.entries((_data$grammarTransiti = data.grammarTransitions.transitions) !== null && _data$grammarTransiti !== void 0 ? _data$grammarTransiti : {}).map(([pos, rule]) => [pos, new Set(rule.allowed)]));
|
|
57
|
+
};
|
|
58
|
+
export const isGrammarDataLoaded = () => precomputedAllowedByPos.size > 0;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch the POS tag and grammar transition payloads from the CDN.
|
|
62
|
+
*
|
|
63
|
+
* Safe to call repeatedly: the in-flight promise is shared, and a failure is not
|
|
64
|
+
* cached so a later call can retry.
|
|
65
|
+
*/
|
|
66
|
+
export const loadGrammarDataAsync = options => {
|
|
67
|
+
var _options$isLocalLLM;
|
|
68
|
+
if (grammarDataPromise) {
|
|
69
|
+
return grammarDataPromise;
|
|
70
|
+
}
|
|
71
|
+
const isLocalLLM = (_options$isLocalLLM = options === null || options === void 0 ? void 0 : options.isLocalLLM) !== null && _options$isLocalLLM !== void 0 ? _options$isLocalLLM : false;
|
|
72
|
+
const surface = options === null || options === void 0 ? void 0 : options.surface;
|
|
73
|
+
grammarDataPromise = (async () => {
|
|
74
|
+
startExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
75
|
+
isLocalLLM,
|
|
76
|
+
...(surface ? {
|
|
77
|
+
surface
|
|
78
|
+
} : {})
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
const [posTagsData, ghostPosTagsData, grammarTransitionsData] = await Promise.all([fetchArtifactJson(ARTIFACT_NAME.POS_TAGS), fetchArtifactJson(ARTIFACT_NAME.GHOST_POS_TAGS), fetchArtifactJson(ARTIFACT_NAME.GRAMMAR_TRANSITIONS)]);
|
|
82
|
+
if ((grammarTransitionsData === null || grammarTransitionsData === void 0 ? void 0 : grammarTransitionsData.transitions) == null) {
|
|
83
|
+
throw new Error(`[scoring-pipeline] ${ARTIFACT_NAME.GRAMMAR_TRANSITIONS} is missing its \`transitions\` wrapper`);
|
|
84
|
+
}
|
|
85
|
+
setGrammarData({
|
|
86
|
+
posTags: posTagsData !== null && posTagsData !== void 0 ? posTagsData : {},
|
|
87
|
+
ghostPosTags: ghostPosTagsData !== null && ghostPosTagsData !== void 0 ? ghostPosTagsData : {},
|
|
88
|
+
grammarTransitions: grammarTransitionsData
|
|
89
|
+
});
|
|
90
|
+
succeedExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
91
|
+
isLocalLLM,
|
|
92
|
+
posTagCount: posTags.size,
|
|
93
|
+
transitionCount: precomputedAllowedByPos.size,
|
|
94
|
+
...(surface ? {
|
|
95
|
+
surface
|
|
96
|
+
} : {})
|
|
97
|
+
});
|
|
98
|
+
if (isAutocompleteDebugEnabled()) {
|
|
99
|
+
// eslint-disable-next-line no-console
|
|
100
|
+
console.log('[scoring-pipeline] Grammar data loaded:', {
|
|
101
|
+
posTagCount: posTags.size,
|
|
102
|
+
ghostPosTagCount: Object.keys(ghostPosTags).length,
|
|
103
|
+
transitionCount: precomputedAllowedByPos.size
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
failExp(EXPERIENCE_NAME.LOAD_GRAMMAR, 'singleton', {
|
|
108
|
+
isLocalLLM,
|
|
109
|
+
errorType: 'network',
|
|
110
|
+
...(surface ? {
|
|
111
|
+
surface
|
|
112
|
+
} : {})
|
|
113
|
+
});
|
|
114
|
+
// Allow a later call to retry the load rather than caching the failure.
|
|
115
|
+
grammarDataPromise = undefined;
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
return grammarDataPromise;
|
|
120
|
+
};
|
|
42
121
|
|
|
43
122
|
// ─── Math ───────────────────────────────────────────────────
|
|
44
123
|
|
|
@@ -87,18 +166,15 @@ function scoreStage1(candidate, contextVector, getWordVector, maxTenantFreq) {
|
|
|
87
166
|
|
|
88
167
|
// ─── Grammar Filter ─────────────────────────────────────────
|
|
89
168
|
|
|
90
|
-
/**
|
|
91
|
-
* GHOST POS DICTIONARY
|
|
92
|
-
* A hardcoded mapping of common structural English words that were stripped
|
|
93
|
-
* from the main domain vocabulary. This allows the grammar filter to understand
|
|
94
|
-
* context without suggesting these words to the user.
|
|
95
|
-
*/
|
|
96
|
-
const ghostPosTags = ghostPosTagsData;
|
|
97
169
|
function applyGrammarFilter(candidates, previousWord) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
170
|
+
// Pass everything through until loadGrammarDataAsync resolves — filtering on a
|
|
171
|
+
// partially loaded ruleset would drop valid candidates.
|
|
172
|
+
if (!previousWord || !isGrammarDataLoaded()) {
|
|
173
|
+
return {
|
|
174
|
+
filtered: candidates,
|
|
175
|
+
grammarMeta: null
|
|
176
|
+
};
|
|
177
|
+
}
|
|
102
178
|
const lowerPrev = previousWord.toLowerCase();
|
|
103
179
|
const prevTags = ghostPosTags[lowerPrev] || posTags.get(lowerPrev);
|
|
104
180
|
if (!prevTags || prevTags.length === 0) {
|
|
@@ -18,9 +18,10 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
18
18
|
|
|
19
19
|
import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
20
20
|
|
|
21
|
-
// The vocabulary, L3 and word-index
|
|
22
|
-
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents
|
|
23
|
-
// the editor's
|
|
21
|
+
// The vocabulary, L3 and word-index payloads are fetched from the CDN in
|
|
22
|
+
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents never enter
|
|
23
|
+
// the editor's bundle and only load when autocomplete is initialised.
|
|
24
|
+
import { ARTIFACT_NAME, fetchArtifactJson, getArtifactUrl } from './artifacts-manifest';
|
|
24
25
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
25
26
|
import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
|
|
26
27
|
import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
|
|
@@ -553,51 +554,11 @@ export const predict = textBefore => {
|
|
|
553
554
|
|
|
554
555
|
// ─── Data Loading ────────────────────────────────────────────────────────────
|
|
555
556
|
|
|
556
|
-
/**
|
|
557
|
-
* Unwrap a dynamically imported JSON module to its parsed value, handling both
|
|
558
|
-
* interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
|
|
559
|
-
* (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
|
|
560
|
-
* modules, where `default` can be a misleading scalar). Named exports are
|
|
561
|
-
* preferred when present. The caller declares the JSON `shape` because a dense
|
|
562
|
-
* array and a sparse numeric-keyed object are emitted identically as named
|
|
563
|
-
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
564
|
-
*/
|
|
565
|
-
function unwrapJsonModule(mod, shape) {
|
|
566
|
-
if (mod == null || typeof mod !== 'object') {
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
const namespace = mod;
|
|
570
|
-
const ownKeys = Object.keys(namespace).filter(k => k !== 'default' && k !== '__esModule');
|
|
571
|
-
if (ownKeys.length > 0) {
|
|
572
|
-
if (shape === 'array') {
|
|
573
|
-
const len = ownKeys.length;
|
|
574
|
-
const arr = new Array(len);
|
|
575
|
-
for (let i = 0; i < len; i++) {
|
|
576
|
-
arr[i] = namespace[String(i)];
|
|
577
|
-
}
|
|
578
|
-
return arr;
|
|
579
|
-
}
|
|
580
|
-
const obj = {};
|
|
581
|
-
for (const k of ownKeys) {
|
|
582
|
-
obj[k] = namespace[k];
|
|
583
|
-
}
|
|
584
|
-
return obj;
|
|
585
|
-
}
|
|
586
|
-
if ('default' in namespace && namespace.default != null) {
|
|
587
|
-
return namespace.default;
|
|
588
|
-
}
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
557
|
export const loadVectorsAsync = async options => {
|
|
592
558
|
var _options$isLocalLLM;
|
|
593
559
|
if (vectorStore || vectorsLoadStarted) {
|
|
594
560
|
return;
|
|
595
561
|
}
|
|
596
|
-
if (!(options !== null && options !== void 0 && options.getBinaryUrl)) {
|
|
597
|
-
// eslint-disable-next-line no-console
|
|
598
|
-
console.warn('[text-predictor] loadVectorsAsync called without a getBinaryUrl — vectors will not load. Pass getVectorsBinaryUrl via plugin options.');
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
562
|
const isLocalLLM = (_options$isLocalLLM = options === null || options === void 0 ? void 0 : options.isLocalLLM) !== null && _options$isLocalLLM !== void 0 ? _options$isLocalLLM : false;
|
|
602
563
|
const surface = options === null || options === void 0 ? void 0 : options.surface;
|
|
603
564
|
vectorsLoadStarted = true;
|
|
@@ -609,7 +570,7 @@ export const loadVectorsAsync = async options => {
|
|
|
609
570
|
});
|
|
610
571
|
let url;
|
|
611
572
|
try {
|
|
612
|
-
url = await
|
|
573
|
+
url = await getArtifactUrl(ARTIFACT_NAME.WORD_VECTORS);
|
|
613
574
|
} catch (e) {
|
|
614
575
|
vectorsLoadStarted = false;
|
|
615
576
|
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
|
|
@@ -624,7 +585,6 @@ export const loadVectorsAsync = async options => {
|
|
|
624
585
|
return;
|
|
625
586
|
}
|
|
626
587
|
try {
|
|
627
|
-
var _wordIndexOuter$index;
|
|
628
588
|
const res = await fetch(url);
|
|
629
589
|
if (!res.ok) {
|
|
630
590
|
vectorsLoadStarted = false;
|
|
@@ -643,16 +603,18 @@ export const loadVectorsAsync = async options => {
|
|
|
643
603
|
const buffer = await res.arrayBuffer();
|
|
644
604
|
const float32 = new Float32Array(buffer);
|
|
645
605
|
|
|
646
|
-
//
|
|
647
|
-
// (e.g. the word "default"
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
606
|
+
// The word index is published either bare or wrapped as `{ "index": {…} }`
|
|
607
|
+
// (the wrapper exists so no real entry, e.g. the word "default", can shadow
|
|
608
|
+
// a bundler-synthesised ESM export). Detect the wrapper by value type, not
|
|
609
|
+
// by key presence — "index" is itself a plausible vocabulary word, and in a
|
|
610
|
+
// bare index every value is a number.
|
|
611
|
+
const wordIndexPayload = await fetchArtifactJson(ARTIFACT_NAME.WORD_INDEX);
|
|
612
|
+
const wrappedIndex = wordIndexPayload === null || wordIndexPayload === void 0 ? void 0 : wordIndexPayload.index;
|
|
613
|
+
const wordIndex = typeof wrappedIndex === 'object' && wrappedIndex !== null ? wrappedIndex : wordIndexPayload !== null && wordIndexPayload !== void 0 ? wordIndexPayload : {};
|
|
652
614
|
const nWords = Object.keys(wordIndex).length;
|
|
653
615
|
if (nWords === 0) {
|
|
654
616
|
// eslint-disable-next-line no-console
|
|
655
|
-
console.warn(
|
|
617
|
+
console.warn(`[text-predictor] ${ARTIFACT_NAME.WORD_INDEX} was empty — semantic scoring will be a no-op.`);
|
|
656
618
|
}
|
|
657
619
|
const dim = float32.length / nWords;
|
|
658
620
|
vectorStore = {
|
|
@@ -712,13 +674,9 @@ export const loadDefaultVocabulary = options => {
|
|
|
712
674
|
} : {})
|
|
713
675
|
});
|
|
714
676
|
try {
|
|
715
|
-
|
|
716
|
-
// chunks so they stay out of the editor's main bundle.
|
|
717
|
-
const [vocabularyModule, l3VocabularyModule] = await Promise.all([import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */'./data/vocabulary_10k.json'), import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-l3-vocabulary" */'./data/l3_vocabulary.json')]);
|
|
718
|
-
const vocabularyData = unwrapJsonModule(vocabularyModule, 'object');
|
|
719
|
-
const l3VocabularyData = unwrapJsonModule(l3VocabularyModule, 'array');
|
|
677
|
+
const [vocabularyData, l3VocabularyData] = await Promise.all([fetchArtifactJson(ARTIFACT_NAME.VOCABULARY), fetchArtifactJson(ARTIFACT_NAME.L3_VOCABULARY)]);
|
|
720
678
|
if ((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null || !Array.isArray(l3VocabularyData)) {
|
|
721
|
-
throw new Error(
|
|
679
|
+
throw new Error(`[text-predictor] unexpected vocabulary payload — ` + `${ARTIFACT_NAME.VOCABULARY} ${(vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null ? 'is missing its `words` wrapper' : 'is valid'}, ` + `${ARTIFACT_NAME.L3_VOCABULARY} ${Array.isArray(l3VocabularyData) ? 'is valid' : 'is not an array'}`);
|
|
722
680
|
}
|
|
723
681
|
const terms = Object.entries(vocabularyData.words).map(([word, stats]) => ({
|
|
724
682
|
word,
|
|
@@ -745,7 +703,7 @@ export const loadDefaultVocabulary = options => {
|
|
|
745
703
|
} catch (e) {
|
|
746
704
|
failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
747
705
|
isLocalLLM,
|
|
748
|
-
errorType: '
|
|
706
|
+
errorType: 'load_error',
|
|
749
707
|
...(surface ? {
|
|
750
708
|
surface
|
|
751
709
|
} : {})
|
|
@@ -34,7 +34,8 @@ import { ConcurrentExperience, ExperiencePerformanceTypes, ExperienceTypes } fro
|
|
|
34
34
|
export var EXPERIENCE_NAME = {
|
|
35
35
|
SLOW_LANE_FETCH: 'slow-lane-fetch',
|
|
36
36
|
LOAD_VOCABULARY: 'load-vocabulary',
|
|
37
|
-
LOAD_VECTORS: 'load-vectors'
|
|
37
|
+
LOAD_VECTORS: 'load-vectors',
|
|
38
|
+
LOAD_GRAMMAR: 'load-grammar'
|
|
38
39
|
};
|
|
39
40
|
var isUfoEnabled = function isUfoEnabled() {
|
|
40
41
|
var _process;
|
|
@@ -52,7 +53,7 @@ var operationConfig = {
|
|
|
52
53
|
})
|
|
53
54
|
}
|
|
54
55
|
};
|
|
55
|
-
var experiences = _defineProperty(_defineProperty(_defineProperty({}, EXPERIENCE_NAME.SLOW_LANE_FETCH, new ConcurrentExperience(EXPERIENCE_NAME.SLOW_LANE_FETCH, operationConfig)), EXPERIENCE_NAME.LOAD_VOCABULARY, new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VOCABULARY, operationConfig)), EXPERIENCE_NAME.LOAD_VECTORS, new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VECTORS, operationConfig));
|
|
56
|
+
var experiences = _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, EXPERIENCE_NAME.SLOW_LANE_FETCH, new ConcurrentExperience(EXPERIENCE_NAME.SLOW_LANE_FETCH, operationConfig)), EXPERIENCE_NAME.LOAD_VOCABULARY, new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VOCABULARY, operationConfig)), EXPERIENCE_NAME.LOAD_VECTORS, new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VECTORS, operationConfig)), EXPERIENCE_NAME.LOAD_GRAMMAR, new ConcurrentExperience(EXPERIENCE_NAME.LOAD_GRAMMAR, operationConfig));
|
|
56
57
|
export var startExp = function startExp(name, id, metadata) {
|
|
57
58
|
if (!isUfoEnabled()) {
|
|
58
59
|
return;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
+
/**
|
|
4
|
+
* Artifacts Manifest Client: resolves the autocomplete model artifacts hosted on
|
|
5
|
+
* the CDN.
|
|
6
|
+
*
|
|
7
|
+
* `GET /gateway/api/v1/autocomplete/artifacts` returns one presigned CDN URL per
|
|
8
|
+
* artifact for the tenant's active model version:
|
|
9
|
+
*
|
|
10
|
+
* { activeVersion, artifacts: [{ name, url, contentType, sizeBytes, checksum }] }
|
|
11
|
+
*
|
|
12
|
+
* Every loader in the plugin resolves its payload through here, so no model data
|
|
13
|
+
* is bundled with the package.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
17
|
+
|
|
18
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manifest artifact names. These are the manifest's lookup keys and are
|
|
22
|
+
* hyphenated, unlike the underscored filenames the artifacts are generated from.
|
|
23
|
+
*/
|
|
24
|
+
export var ARTIFACT_NAME = {
|
|
25
|
+
FIRST_TOKEN_TO_WORDS: 'first-token-to-words.json',
|
|
26
|
+
GHOST_POS_TAGS: 'ghost-pos-tags.json',
|
|
27
|
+
GRAMMAR_TRANSITIONS: 'grammar-transitions-10k.json',
|
|
28
|
+
L3_VOCABULARY: 'l3-vocabulary.json',
|
|
29
|
+
POS_TAGS: 'combined-l2-l3-pos-tags.json',
|
|
30
|
+
VOCABULARY: 'vocabulary-10k.json',
|
|
31
|
+
WORD_INDEX: 'word-index-10k.json',
|
|
32
|
+
WORD_VECTORS: 'word-vectors-10k.bin'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A single entry in the artifacts manifest. */
|
|
36
|
+
|
|
37
|
+
/** Response body of the artifacts endpoint. */
|
|
38
|
+
|
|
39
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
var MANIFEST_ENDPOINT = '/gateway/api/v1/autocomplete/artifacts';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Presigned artifact URLs expire ~10 minutes after the manifest is issued, so
|
|
45
|
+
* the manifest is re-requested well inside that window instead of being cached
|
|
46
|
+
* for the session and handing out dead URLs to a late loader.
|
|
47
|
+
*/
|
|
48
|
+
var MANIFEST_TTL_MS = 5 * 60 * 1000;
|
|
49
|
+
|
|
50
|
+
// ─── Manifest ────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
var manifestPromise;
|
|
53
|
+
var manifestRequestedAt = 0;
|
|
54
|
+
|
|
55
|
+
/** Test-only: drops the memoised manifest so the next lookup re-requests it. */
|
|
56
|
+
export var resetArtifactsManifestCache = function resetArtifactsManifestCache() {
|
|
57
|
+
manifestPromise = undefined;
|
|
58
|
+
manifestRequestedAt = 0;
|
|
59
|
+
};
|
|
60
|
+
var requestManifest = /*#__PURE__*/function () {
|
|
61
|
+
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
62
|
+
var res, body;
|
|
63
|
+
return _regeneratorRuntime.wrap(function (_context) {
|
|
64
|
+
while (1) switch (_context.prev = _context.next) {
|
|
65
|
+
case 0:
|
|
66
|
+
_context.next = 1;
|
|
67
|
+
return fetch(MANIFEST_ENDPOINT, {
|
|
68
|
+
method: 'GET',
|
|
69
|
+
headers: {
|
|
70
|
+
'x-experience-id': 'confluence-smart-typeahead-artifacts',
|
|
71
|
+
'x-product': 'confluence'
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
case 1:
|
|
75
|
+
res = _context.sent;
|
|
76
|
+
if (res.ok) {
|
|
77
|
+
_context.next = 2;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
throw new Error("[autocomplete-artifacts] Manifest request failed: ".concat(res.status));
|
|
81
|
+
case 2:
|
|
82
|
+
_context.next = 3;
|
|
83
|
+
return res.json();
|
|
84
|
+
case 3:
|
|
85
|
+
body = _context.sent;
|
|
86
|
+
if (!(!Array.isArray(body === null || body === void 0 ? void 0 : body.artifacts) || body.artifacts.length === 0)) {
|
|
87
|
+
_context.next = 4;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
throw new Error('[autocomplete-artifacts] Manifest response contained no artifacts');
|
|
91
|
+
case 4:
|
|
92
|
+
if (isAutocompleteDebugEnabled()) {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log('[autocomplete-artifacts] Manifest loaded:', {
|
|
95
|
+
activeVersion: body.activeVersion,
|
|
96
|
+
names: body.artifacts.map(function (artifact) {
|
|
97
|
+
return artifact.name;
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return _context.abrupt("return", new Map(body.artifacts.map(function (artifact) {
|
|
102
|
+
return [artifact.name, artifact];
|
|
103
|
+
})));
|
|
104
|
+
case 5:
|
|
105
|
+
case "end":
|
|
106
|
+
return _context.stop();
|
|
107
|
+
}
|
|
108
|
+
}, _callee);
|
|
109
|
+
}));
|
|
110
|
+
return function requestManifest() {
|
|
111
|
+
return _ref.apply(this, arguments);
|
|
112
|
+
};
|
|
113
|
+
}();
|
|
114
|
+
var getManifest = function getManifest() {
|
|
115
|
+
if (manifestPromise && Date.now() - manifestRequestedAt < MANIFEST_TTL_MS) {
|
|
116
|
+
return manifestPromise;
|
|
117
|
+
}
|
|
118
|
+
manifestRequestedAt = Date.now();
|
|
119
|
+
manifestPromise = requestManifest().catch(function (e) {
|
|
120
|
+
// Never cache a rejection — a transient failure would otherwise stop every
|
|
121
|
+
// later loader in the session from retrying.
|
|
122
|
+
resetArtifactsManifestCache();
|
|
123
|
+
throw e;
|
|
124
|
+
});
|
|
125
|
+
return manifestPromise;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve the CDN URL for an artifact.
|
|
132
|
+
*
|
|
133
|
+
* :raises Error: when the manifest cannot be fetched or omits the artifact.
|
|
134
|
+
*/
|
|
135
|
+
export var getArtifactUrl = /*#__PURE__*/function () {
|
|
136
|
+
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(name) {
|
|
137
|
+
var _manifest$get;
|
|
138
|
+
var manifest, url;
|
|
139
|
+
return _regeneratorRuntime.wrap(function (_context2) {
|
|
140
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
141
|
+
case 0:
|
|
142
|
+
_context2.next = 1;
|
|
143
|
+
return getManifest();
|
|
144
|
+
case 1:
|
|
145
|
+
manifest = _context2.sent;
|
|
146
|
+
url = (_manifest$get = manifest.get(name)) === null || _manifest$get === void 0 ? void 0 : _manifest$get.url;
|
|
147
|
+
if (url) {
|
|
148
|
+
_context2.next = 2;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
throw new Error("[autocomplete-artifacts] Manifest has no URL for \"".concat(name, "\""));
|
|
152
|
+
case 2:
|
|
153
|
+
return _context2.abrupt("return", url);
|
|
154
|
+
case 3:
|
|
155
|
+
case "end":
|
|
156
|
+
return _context2.stop();
|
|
157
|
+
}
|
|
158
|
+
}, _callee2);
|
|
159
|
+
}));
|
|
160
|
+
return function getArtifactUrl(_x) {
|
|
161
|
+
return _ref2.apply(this, arguments);
|
|
162
|
+
};
|
|
163
|
+
}();
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Download and parse a JSON artifact. The presigned URL carries its own auth
|
|
167
|
+
* token, so the request is sent without Atlassian headers or credentials.
|
|
168
|
+
*
|
|
169
|
+
* :raises Error: when the manifest lookup or the download fails.
|
|
170
|
+
*/
|
|
171
|
+
export var fetchArtifactJson = /*#__PURE__*/function () {
|
|
172
|
+
var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(name) {
|
|
173
|
+
var url, res;
|
|
174
|
+
return _regeneratorRuntime.wrap(function (_context3) {
|
|
175
|
+
while (1) switch (_context3.prev = _context3.next) {
|
|
176
|
+
case 0:
|
|
177
|
+
_context3.next = 1;
|
|
178
|
+
return getArtifactUrl(name);
|
|
179
|
+
case 1:
|
|
180
|
+
url = _context3.sent;
|
|
181
|
+
_context3.next = 2;
|
|
182
|
+
return fetch(url);
|
|
183
|
+
case 2:
|
|
184
|
+
res = _context3.sent;
|
|
185
|
+
if (res.ok) {
|
|
186
|
+
_context3.next = 3;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
throw new Error("[autocomplete-artifacts] Failed to download \"".concat(name, "\": ").concat(res.status));
|
|
190
|
+
case 3:
|
|
191
|
+
_context3.next = 4;
|
|
192
|
+
return res.json();
|
|
193
|
+
case 4:
|
|
194
|
+
return _context3.abrupt("return", _context3.sent);
|
|
195
|
+
case 5:
|
|
196
|
+
case "end":
|
|
197
|
+
return _context3.stop();
|
|
198
|
+
}
|
|
199
|
+
}, _callee3);
|
|
200
|
+
}));
|
|
201
|
+
return function fetchArtifactJson(_x2) {
|
|
202
|
+
return _ref3.apply(this, arguments);
|
|
203
|
+
};
|
|
204
|
+
}();
|
|
@@ -14,6 +14,7 @@ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
|
14
14
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
15
15
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
16
16
|
import { createLocalSlowLaneClient } from './local-slow-lane-client';
|
|
17
|
+
import { loadGrammarDataAsync } from './scoring-pipeline';
|
|
17
18
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
18
19
|
import { predict, loadDefaultVocabulary, loadVectorsAsync, incrementSessionFreq, ingestDocumentPage } from './text-predictor';
|
|
19
20
|
export var autocompletePluginKey = new PluginKey('autocomplete');
|
|
@@ -671,7 +672,7 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
671
672
|
return accepted;
|
|
672
673
|
},
|
|
673
674
|
focus: function focus() {
|
|
674
|
-
var _options$useLocalMode, _options$useLocalMode2;
|
|
675
|
+
var _options$useLocalMode, _options$useLocalMode2, _options$useLocalMode3;
|
|
675
676
|
if (!isAutocompleteEnabled) {
|
|
676
677
|
return false;
|
|
677
678
|
}
|
|
@@ -684,7 +685,6 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
684
685
|
});
|
|
685
686
|
});
|
|
686
687
|
loadVectorsAsync({
|
|
687
|
-
getBinaryUrl: options === null || options === void 0 ? void 0 : options.getVectorsBinaryUrl,
|
|
688
688
|
isLocalLLM: (_options$useLocalMode2 = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode2 !== void 0 ? _options$useLocalMode2 : false,
|
|
689
689
|
surface: surface
|
|
690
690
|
}).catch(function (error) {
|
|
@@ -692,6 +692,14 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
692
692
|
location: 'editor-plugin-autocomplete/loadVectorsAsync'
|
|
693
693
|
});
|
|
694
694
|
});
|
|
695
|
+
loadGrammarDataAsync({
|
|
696
|
+
isLocalLLM: (_options$useLocalMode3 = options === null || options === void 0 ? void 0 : options.useLocalModel) !== null && _options$useLocalMode3 !== void 0 ? _options$useLocalMode3 : false,
|
|
697
|
+
surface: surface
|
|
698
|
+
}).catch(function (error) {
|
|
699
|
+
logException(error, {
|
|
700
|
+
location: 'editor-plugin-autocomplete/loadGrammarDataAsync'
|
|
701
|
+
});
|
|
702
|
+
});
|
|
695
703
|
if (!hasIngestedPage) {
|
|
696
704
|
hasIngestedPage = true;
|
|
697
705
|
refreshContext({
|