@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/compass.yml +2 -2
  3. package/dist/cjs/analytics/ufo.js +3 -2
  4. package/dist/cjs/pm-plugins/artifacts-manifest.js +210 -0
  5. package/dist/cjs/pm-plugins/autocomplete-plugin.js +10 -2
  6. package/dist/cjs/pm-plugins/local-slow-lane-client.js +68 -153
  7. package/dist/cjs/pm-plugins/scoring-pipeline.js +130 -28
  8. package/dist/cjs/pm-plugins/text-predictor.js +45 -106
  9. package/dist/es2019/analytics/ufo.js +4 -2
  10. package/dist/es2019/pm-plugins/artifacts-manifest.js +126 -0
  11. package/dist/es2019/pm-plugins/autocomplete-plugin.js +10 -2
  12. package/dist/es2019/pm-plugins/local-slow-lane-client.js +8 -78
  13. package/dist/es2019/pm-plugins/scoring-pipeline.js +97 -21
  14. package/dist/es2019/pm-plugins/text-predictor.js +17 -59
  15. package/dist/esm/analytics/ufo.js +3 -2
  16. package/dist/esm/pm-plugins/artifacts-manifest.js +204 -0
  17. package/dist/esm/pm-plugins/autocomplete-plugin.js +10 -2
  18. package/dist/esm/pm-plugins/local-slow-lane-client.js +67 -148
  19. package/dist/esm/pm-plugins/scoring-pipeline.js +128 -26
  20. package/dist/esm/pm-plugins/text-predictor.js +45 -98
  21. package/dist/types/analytics/ufo.d.ts +1 -0
  22. package/dist/types/pm-plugins/artifacts-manifest.d.ts +55 -0
  23. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +5 -11
  24. package/dist/types/pm-plugins/scoring-pipeline.d.ts +14 -0
  25. package/dist/types/pm-plugins/text-predictor.d.ts +0 -1
  26. package/package.json +3 -6
  27. package/src/analytics/ufo.ts +5 -0
  28. package/src/pm-plugins/artifacts-manifest.ts +151 -0
  29. package/src/pm-plugins/autocomplete-plugin.ts +14 -12
  30. package/src/pm-plugins/local-slow-lane-client.ts +10 -92
  31. package/src/pm-plugins/scoring-pipeline.ts +115 -23
  32. package/src/pm-plugins/text-predictor.ts +28 -76
  33. package/dist/cjs/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
  34. package/dist/cjs/pm-plugins/data/first_token_to_words.json +0 -1
  35. package/dist/cjs/pm-plugins/data/ghost_pos_tags.json +0 -43
  36. package/dist/cjs/pm-plugins/data/grammar_transitions_10k.json +0 -46
  37. package/dist/cjs/pm-plugins/data/l3_vocabulary.json +0 -20002
  38. package/dist/cjs/pm-plugins/data/vocabulary_10k.json +0 -38794
  39. package/dist/cjs/pm-plugins/data/word_index_10k.json +0 -7762
  40. package/dist/es2019/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
  41. package/dist/es2019/pm-plugins/data/first_token_to_words.json +0 -1
  42. package/dist/es2019/pm-plugins/data/ghost_pos_tags.json +0 -43
  43. package/dist/es2019/pm-plugins/data/grammar_transitions_10k.json +0 -46
  44. package/dist/es2019/pm-plugins/data/l3_vocabulary.json +0 -20002
  45. package/dist/es2019/pm-plugins/data/vocabulary_10k.json +0 -38794
  46. package/dist/es2019/pm-plugins/data/word_index_10k.json +0 -7762
  47. package/dist/esm/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
  48. package/dist/esm/pm-plugins/data/first_token_to_words.json +0 -1
  49. package/dist/esm/pm-plugins/data/ghost_pos_tags.json +0 -43
  50. package/dist/esm/pm-plugins/data/grammar_transitions_10k.json +0 -46
  51. package/dist/esm/pm-plugins/data/l3_vocabulary.json +0 -20002
  52. package/dist/esm/pm-plugins/data/vocabulary_10k.json +0 -38794
  53. package/dist/esm/pm-plugins/data/word_index_10k.json +0 -7762
  54. package/src/pm-plugins/data/combined_l2_l3_pos_tags.json +0 -73571
  55. package/src/pm-plugins/data/first_token_to_words.json +0 -1
  56. package/src/pm-plugins/data/ghost_pos_tags.json +0 -43
  57. package/src/pm-plugins/data/grammar_transitions_10k.json +0 -46
  58. package/src/pm-plugins/data/l3_vocabulary.json +0 -20002
  59. package/src/pm-plugins/data/vocabulary_10k.json +0 -38794
  60. package/src/pm-plugins/data/word-vectors_10k.bin +0 -3
  61. 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 posTagsData from './data/combined_l2_l3_pos_tags.json';
9
- import ghostPosTagsData from './data/ghost_pos_tags.json';
10
- import grammarTransitionsData from './data/grammar_transitions_10k.json';
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 (loaded once on import) ───────────────────
34
+ // ─── Grammar Data (fetched once from the CDN) ────────────────
35
+
36
+ let posTags = new Map();
32
37
 
33
- const posTags = new Map(Object.entries(posTagsData));
34
- const grammarTransitions = grammarTransitionsData;
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
- * Built once at module load from grammarTransitions so applyGrammarFilter
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
- const precomputedAllowedByPos = new Map(Object.entries(grammarTransitions.transitions).map(([pos, rule]) => [pos, new Set(rule.allowed)]));
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
- if (!previousWord) return {
99
- filtered: candidates,
100
- grammarMeta: null
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 JSON payloads are dynamically imported in
22
- // loadDefaultVocabulary / loadVectorsAsync so their (large) contents stay out of
23
- // the editor's main chunk and only load when autocomplete is initialised.
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 options.getBinaryUrl();
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
- // word_index_10k.json is wrapped as `{ "index": {…} }` so no real entry
647
- // (e.g. the word "default") can shadow the synthetic ESM `default` export
648
- // the bundler creates for dynamically-imported JSON.
649
- const wordIndexModule = await import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-word-index-10k" */'./data/word_index_10k.json');
650
- const wordIndexOuter = unwrapJsonModule(wordIndexModule, 'object');
651
- const wordIndex = (_wordIndexOuter$index = wordIndexOuter === null || wordIndexOuter === void 0 ? void 0 : wordIndexOuter.index) !== null && _wordIndexOuter$index !== void 0 ? _wordIndexOuter$index : {};
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('[text-predictor] word_index_10k.json missing its `index` wrapper — wordIndex is empty, semantic scoring will be a no-op.');
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
- // The L2 vocabulary and L3 word list are code-split into their own async
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('[text-predictor] vocabulary JSON modules could not be unwrapped');
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: 'parse_error',
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({