@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
1
|
import _typeof from "@babel/runtime/helpers/typeof";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
3
3
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
4
4
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
5
5
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
@@ -29,9 +29,10 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
29
29
|
|
|
30
30
|
import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
|
|
31
31
|
|
|
32
|
-
// The vocabulary, L3 and word-index
|
|
33
|
-
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents
|
|
34
|
-
// the editor's
|
|
32
|
+
// The vocabulary, L3 and word-index payloads are fetched from the CDN in
|
|
33
|
+
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents never enter
|
|
34
|
+
// the editor's bundle and only load when autocomplete is initialised.
|
|
35
|
+
import { ARTIFACT_NAME, fetchArtifactJson, getArtifactUrl } from './artifacts-manifest';
|
|
35
36
|
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
36
37
|
import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
|
|
37
38
|
import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
|
|
@@ -685,56 +686,10 @@ export var predict = function predict(textBefore) {
|
|
|
685
686
|
|
|
686
687
|
// ─── Data Loading ────────────────────────────────────────────────────────────
|
|
687
688
|
|
|
688
|
-
/**
|
|
689
|
-
* Unwrap a dynamically imported JSON module to its parsed value, handling both
|
|
690
|
-
* interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
|
|
691
|
-
* (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
|
|
692
|
-
* modules, where `default` can be a misleading scalar). Named exports are
|
|
693
|
-
* preferred when present. The caller declares the JSON `shape` because a dense
|
|
694
|
-
* array and a sparse numeric-keyed object are emitted identically as named
|
|
695
|
-
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
696
|
-
*/
|
|
697
|
-
function unwrapJsonModule(mod, shape) {
|
|
698
|
-
if (mod == null || _typeof(mod) !== 'object') {
|
|
699
|
-
return null;
|
|
700
|
-
}
|
|
701
|
-
var namespace = mod;
|
|
702
|
-
var ownKeys = Object.keys(namespace).filter(function (k) {
|
|
703
|
-
return k !== 'default' && k !== '__esModule';
|
|
704
|
-
});
|
|
705
|
-
if (ownKeys.length > 0) {
|
|
706
|
-
if (shape === 'array') {
|
|
707
|
-
var len = ownKeys.length;
|
|
708
|
-
var arr = new Array(len);
|
|
709
|
-
for (var i = 0; i < len; i++) {
|
|
710
|
-
arr[i] = namespace[String(i)];
|
|
711
|
-
}
|
|
712
|
-
return arr;
|
|
713
|
-
}
|
|
714
|
-
var obj = {};
|
|
715
|
-
var _iterator1 = _createForOfIteratorHelper(ownKeys),
|
|
716
|
-
_step1;
|
|
717
|
-
try {
|
|
718
|
-
for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
|
|
719
|
-
var k = _step1.value;
|
|
720
|
-
obj[k] = namespace[k];
|
|
721
|
-
}
|
|
722
|
-
} catch (err) {
|
|
723
|
-
_iterator1.e(err);
|
|
724
|
-
} finally {
|
|
725
|
-
_iterator1.f();
|
|
726
|
-
}
|
|
727
|
-
return obj;
|
|
728
|
-
}
|
|
729
|
-
if ('default' in namespace && namespace.default != null) {
|
|
730
|
-
return namespace.default;
|
|
731
|
-
}
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
689
|
export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
735
690
|
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
|
|
736
691
|
var _options$isLocalLLM;
|
|
737
|
-
var isLocalLLM, surface, url,
|
|
692
|
+
var isLocalLLM, surface, url, res, buffer, float32, wordIndexPayload, wrappedIndex, wordIndex, nWords, dim, _t, _t2;
|
|
738
693
|
return _regeneratorRuntime.wrap(function (_context) {
|
|
739
694
|
while (1) switch (_context.prev = _context.next) {
|
|
740
695
|
case 0:
|
|
@@ -744,14 +699,6 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
744
699
|
}
|
|
745
700
|
return _context.abrupt("return");
|
|
746
701
|
case 1:
|
|
747
|
-
if (options !== null && options !== void 0 && options.getBinaryUrl) {
|
|
748
|
-
_context.next = 2;
|
|
749
|
-
break;
|
|
750
|
-
}
|
|
751
|
-
// eslint-disable-next-line no-console
|
|
752
|
-
console.warn('[text-predictor] loadVectorsAsync called without a getBinaryUrl — vectors will not load. Pass getVectorsBinaryUrl via plugin options.');
|
|
753
|
-
return _context.abrupt("return");
|
|
754
|
-
case 2:
|
|
755
702
|
isLocalLLM = (_options$isLocalLLM = options === null || options === void 0 ? void 0 : options.isLocalLLM) !== null && _options$isLocalLLM !== void 0 ? _options$isLocalLLM : false;
|
|
756
703
|
surface = options === null || options === void 0 ? void 0 : options.surface;
|
|
757
704
|
vectorsLoadStarted = true;
|
|
@@ -760,16 +707,16 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
760
707
|
}, surface ? {
|
|
761
708
|
surface: surface
|
|
762
709
|
} : {}));
|
|
763
|
-
_context.prev =
|
|
764
|
-
_context.next =
|
|
765
|
-
return
|
|
766
|
-
case
|
|
710
|
+
_context.prev = 2;
|
|
711
|
+
_context.next = 3;
|
|
712
|
+
return getArtifactUrl(ARTIFACT_NAME.WORD_VECTORS);
|
|
713
|
+
case 3:
|
|
767
714
|
url = _context.sent;
|
|
768
|
-
_context.next =
|
|
715
|
+
_context.next = 5;
|
|
769
716
|
break;
|
|
770
|
-
case
|
|
771
|
-
_context.prev =
|
|
772
|
-
_t = _context["catch"](
|
|
717
|
+
case 4:
|
|
718
|
+
_context.prev = 4;
|
|
719
|
+
_t = _context["catch"](2);
|
|
773
720
|
vectorsLoadStarted = false;
|
|
774
721
|
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', _objectSpread({
|
|
775
722
|
isLocalLLM: isLocalLLM,
|
|
@@ -780,14 +727,14 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
780
727
|
// eslint-disable-next-line no-console
|
|
781
728
|
console.warn('[text-predictor] Failed to resolve vectors URL:', _t);
|
|
782
729
|
return _context.abrupt("return");
|
|
783
|
-
case
|
|
784
|
-
_context.prev =
|
|
785
|
-
_context.next =
|
|
730
|
+
case 5:
|
|
731
|
+
_context.prev = 5;
|
|
732
|
+
_context.next = 6;
|
|
786
733
|
return fetch(url);
|
|
787
|
-
case
|
|
734
|
+
case 6:
|
|
788
735
|
res = _context.sent;
|
|
789
736
|
if (res.ok) {
|
|
790
|
-
_context.next =
|
|
737
|
+
_context.next = 7;
|
|
791
738
|
break;
|
|
792
739
|
}
|
|
793
740
|
vectorsLoadStarted = false;
|
|
@@ -801,24 +748,26 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
801
748
|
// eslint-disable-next-line no-console
|
|
802
749
|
console.warn("[text-predictor] Failed to load vectors: ".concat(res.status));
|
|
803
750
|
return _context.abrupt("return");
|
|
751
|
+
case 7:
|
|
752
|
+
_context.next = 8;
|
|
753
|
+
return res.arrayBuffer();
|
|
804
754
|
case 8:
|
|
755
|
+
buffer = _context.sent;
|
|
756
|
+
float32 = new Float32Array(buffer); // The word index is published either bare or wrapped as `{ "index": {…} }`
|
|
757
|
+
// (the wrapper exists so no real entry, e.g. the word "default", can shadow
|
|
758
|
+
// a bundler-synthesised ESM export). Detect the wrapper by value type, not
|
|
759
|
+
// by key presence — "index" is itself a plausible vocabulary word, and in a
|
|
760
|
+
// bare index every value is a number.
|
|
805
761
|
_context.next = 9;
|
|
806
|
-
return
|
|
762
|
+
return fetchArtifactJson(ARTIFACT_NAME.WORD_INDEX);
|
|
807
763
|
case 9:
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
// the bundler creates for dynamically-imported JSON.
|
|
812
|
-
_context.next = 10;
|
|
813
|
-
return import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-word-index-10k" */'./data/word_index_10k.json');
|
|
814
|
-
case 10:
|
|
815
|
-
wordIndexModule = _context.sent;
|
|
816
|
-
wordIndexOuter = unwrapJsonModule(wordIndexModule, 'object');
|
|
817
|
-
wordIndex = (_wordIndexOuter$index = wordIndexOuter === null || wordIndexOuter === void 0 ? void 0 : wordIndexOuter.index) !== null && _wordIndexOuter$index !== void 0 ? _wordIndexOuter$index : {};
|
|
764
|
+
wordIndexPayload = _context.sent;
|
|
765
|
+
wrappedIndex = wordIndexPayload === null || wordIndexPayload === void 0 ? void 0 : wordIndexPayload.index;
|
|
766
|
+
wordIndex = _typeof(wrappedIndex) === 'object' && wrappedIndex !== null ? wrappedIndex : wordIndexPayload !== null && wordIndexPayload !== void 0 ? wordIndexPayload : {};
|
|
818
767
|
nWords = Object.keys(wordIndex).length;
|
|
819
768
|
if (nWords === 0) {
|
|
820
769
|
// eslint-disable-next-line no-console
|
|
821
|
-
console.warn(
|
|
770
|
+
console.warn("[text-predictor] ".concat(ARTIFACT_NAME.WORD_INDEX, " was empty \u2014 semantic scoring will be a no-op."));
|
|
822
771
|
}
|
|
823
772
|
dim = float32.length / nWords;
|
|
824
773
|
vectorStore = {
|
|
@@ -842,11 +791,11 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
842
791
|
sizeBytes: float32.byteLength
|
|
843
792
|
});
|
|
844
793
|
}
|
|
845
|
-
_context.next =
|
|
794
|
+
_context.next = 11;
|
|
846
795
|
break;
|
|
847
|
-
case
|
|
848
|
-
_context.prev =
|
|
849
|
-
_t2 = _context["catch"](
|
|
796
|
+
case 10:
|
|
797
|
+
_context.prev = 10;
|
|
798
|
+
_t2 = _context["catch"](5);
|
|
850
799
|
vectorsLoadStarted = false;
|
|
851
800
|
failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', _objectSpread({
|
|
852
801
|
isLocalLLM: isLocalLLM,
|
|
@@ -856,11 +805,11 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
856
805
|
} : {}));
|
|
857
806
|
// eslint-disable-next-line no-console
|
|
858
807
|
console.warn('[text-predictor] Failed to load vectors:', _t2);
|
|
859
|
-
case
|
|
808
|
+
case 11:
|
|
860
809
|
case "end":
|
|
861
810
|
return _context.stop();
|
|
862
811
|
}
|
|
863
|
-
}, _callee, null, [[
|
|
812
|
+
}, _callee, null, [[2, 4], [5, 10]]);
|
|
864
813
|
}));
|
|
865
814
|
return function loadVectorsAsync(_x) {
|
|
866
815
|
return _ref6.apply(this, arguments);
|
|
@@ -881,7 +830,7 @@ export var loadDefaultVocabulary = function loadDefaultVocabulary(options) {
|
|
|
881
830
|
var isLocalLLM = (_options$isLocalLLM2 = options === null || options === void 0 ? void 0 : options.isLocalLLM) !== null && _options$isLocalLLM2 !== void 0 ? _options$isLocalLLM2 : false;
|
|
882
831
|
var surface = options === null || options === void 0 ? void 0 : options.surface;
|
|
883
832
|
vocabularyLoadPromise = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
|
|
884
|
-
var _yield$Promise$all, _yield$Promise$all2,
|
|
833
|
+
var _yield$Promise$all, _yield$Promise$all2, vocabularyData, l3VocabularyData, terms, _t3;
|
|
885
834
|
return _regeneratorRuntime.wrap(function (_context2) {
|
|
886
835
|
while (1) switch (_context2.prev = _context2.next) {
|
|
887
836
|
case 0:
|
|
@@ -892,19 +841,17 @@ export var loadDefaultVocabulary = function loadDefaultVocabulary(options) {
|
|
|
892
841
|
} : {}));
|
|
893
842
|
_context2.prev = 1;
|
|
894
843
|
_context2.next = 2;
|
|
895
|
-
return Promise.all([
|
|
844
|
+
return Promise.all([fetchArtifactJson(ARTIFACT_NAME.VOCABULARY), fetchArtifactJson(ARTIFACT_NAME.L3_VOCABULARY)]);
|
|
896
845
|
case 2:
|
|
897
846
|
_yield$Promise$all = _context2.sent;
|
|
898
847
|
_yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 2);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
vocabularyData = unwrapJsonModule(vocabularyModule, 'object');
|
|
902
|
-
l3VocabularyData = unwrapJsonModule(l3VocabularyModule, 'array');
|
|
848
|
+
vocabularyData = _yield$Promise$all2[0];
|
|
849
|
+
l3VocabularyData = _yield$Promise$all2[1];
|
|
903
850
|
if (!((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null || !Array.isArray(l3VocabularyData))) {
|
|
904
851
|
_context2.next = 3;
|
|
905
852
|
break;
|
|
906
853
|
}
|
|
907
|
-
throw new Error(
|
|
854
|
+
throw new Error("[text-predictor] unexpected vocabulary payload \u2014 " + "".concat(ARTIFACT_NAME.VOCABULARY, " ").concat((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null ? 'is missing its `words` wrapper' : 'is valid', ", ") + "".concat(ARTIFACT_NAME.L3_VOCABULARY, " ").concat(Array.isArray(l3VocabularyData) ? 'is valid' : 'is not an array'));
|
|
908
855
|
case 3:
|
|
909
856
|
terms = Object.entries(vocabularyData.words).map(function (_ref8) {
|
|
910
857
|
var _ref9 = _slicedToArray(_ref8, 2),
|
|
@@ -937,7 +884,7 @@ export var loadDefaultVocabulary = function loadDefaultVocabulary(options) {
|
|
|
937
884
|
_t3 = _context2["catch"](1);
|
|
938
885
|
failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', _objectSpread({
|
|
939
886
|
isLocalLLM: isLocalLLM,
|
|
940
|
-
errorType: '
|
|
887
|
+
errorType: 'load_error'
|
|
941
888
|
}, surface ? {
|
|
942
889
|
surface: surface
|
|
943
890
|
} : {}));
|
|
@@ -30,6 +30,7 @@ export declare const EXPERIENCE_NAME: {
|
|
|
30
30
|
readonly SLOW_LANE_FETCH: 'slow-lane-fetch';
|
|
31
31
|
readonly LOAD_VOCABULARY: 'load-vocabulary';
|
|
32
32
|
readonly LOAD_VECTORS: 'load-vectors';
|
|
33
|
+
readonly LOAD_GRAMMAR: 'load-grammar';
|
|
33
34
|
};
|
|
34
35
|
export type AutocompleteExperienceName = (typeof EXPERIENCE_NAME)[keyof typeof EXPERIENCE_NAME];
|
|
35
36
|
export declare const startExp: (name: AutocompleteExperienceName, id: string, metadata?: CustomData) => void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifacts Manifest Client: resolves the autocomplete model artifacts hosted on
|
|
3
|
+
* the CDN.
|
|
4
|
+
*
|
|
5
|
+
* `GET /gateway/api/v1/autocomplete/artifacts` returns one presigned CDN URL per
|
|
6
|
+
* artifact for the tenant's active model version:
|
|
7
|
+
*
|
|
8
|
+
* { activeVersion, artifacts: [{ name, url, contentType, sizeBytes, checksum }] }
|
|
9
|
+
*
|
|
10
|
+
* Every loader in the plugin resolves its payload through here, so no model data
|
|
11
|
+
* is bundled with the package.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Manifest artifact names. These are the manifest's lookup keys and are
|
|
15
|
+
* hyphenated, unlike the underscored filenames the artifacts are generated from.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ARTIFACT_NAME: {
|
|
18
|
+
readonly FIRST_TOKEN_TO_WORDS: 'first-token-to-words.json';
|
|
19
|
+
readonly GHOST_POS_TAGS: 'ghost-pos-tags.json';
|
|
20
|
+
readonly GRAMMAR_TRANSITIONS: 'grammar-transitions-10k.json';
|
|
21
|
+
readonly L3_VOCABULARY: 'l3-vocabulary.json';
|
|
22
|
+
readonly POS_TAGS: 'combined-l2-l3-pos-tags.json';
|
|
23
|
+
readonly VOCABULARY: 'vocabulary-10k.json';
|
|
24
|
+
readonly WORD_INDEX: 'word-index-10k.json';
|
|
25
|
+
readonly WORD_VECTORS: 'word-vectors-10k.bin';
|
|
26
|
+
};
|
|
27
|
+
export type ArtifactName = (typeof ARTIFACT_NAME)[keyof typeof ARTIFACT_NAME];
|
|
28
|
+
/** A single entry in the artifacts manifest. */
|
|
29
|
+
export interface AutocompleteArtifact {
|
|
30
|
+
checksum: string;
|
|
31
|
+
contentType: string;
|
|
32
|
+
name: string;
|
|
33
|
+
sizeBytes: number;
|
|
34
|
+
url: string;
|
|
35
|
+
}
|
|
36
|
+
/** Response body of the artifacts endpoint. */
|
|
37
|
+
export interface AutocompleteArtifactsResponse {
|
|
38
|
+
activeVersion: string;
|
|
39
|
+
artifacts: AutocompleteArtifact[];
|
|
40
|
+
}
|
|
41
|
+
/** Test-only: drops the memoised manifest so the next lookup re-requests it. */
|
|
42
|
+
export declare const resetArtifactsManifestCache: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the CDN URL for an artifact.
|
|
45
|
+
*
|
|
46
|
+
* :raises Error: when the manifest cannot be fetched or omits the artifact.
|
|
47
|
+
*/
|
|
48
|
+
export declare const getArtifactUrl: (name: ArtifactName) => Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Download and parse a JSON artifact. The presigned URL carries its own auth
|
|
51
|
+
* token, so the request is sent without Atlassian headers or credentials.
|
|
52
|
+
*
|
|
53
|
+
* :raises Error: when the manifest lookup or the download fails.
|
|
54
|
+
*/
|
|
55
|
+
export declare const fetchArtifactJson: <T>(name: ArtifactName) => Promise<T>;
|
|
@@ -29,22 +29,11 @@ export interface AutocompleteContext {
|
|
|
29
29
|
siblingCommentsContents?: string[];
|
|
30
30
|
}
|
|
31
31
|
export interface AutocompletePluginOptions {
|
|
32
|
-
/**
|
|
33
|
-
* Product/editor surface where autocomplete runs (e.g. "comment", "chat").
|
|
34
|
-
* Added to analytics and UFO metadata for cross-surface reporting.
|
|
35
|
-
*/
|
|
36
|
-
surface?: string;
|
|
37
32
|
/**
|
|
38
33
|
* Async function called once on first editor focus to retrieve context for
|
|
39
34
|
* word-frequency boosting. Called lazily so the preset can remain synchronous.
|
|
40
35
|
*/
|
|
41
36
|
getContext?: () => Promise<AutocompleteContext | undefined>;
|
|
42
|
-
/**
|
|
43
|
-
* Async function that resolves to a URL for the word vectors binary file.
|
|
44
|
-
* When provided, this takes precedence over the bundled asset URL.
|
|
45
|
-
* Use this to serve vectors from a CDN or media service in production.
|
|
46
|
-
*/
|
|
47
|
-
getVectorsBinaryUrl?: () => Promise<string>;
|
|
48
37
|
/**
|
|
49
38
|
* User locale used to determine whether autocomplete should run.
|
|
50
39
|
* Defaults to browser locale when omitted.
|
|
@@ -56,6 +45,11 @@ export interface AutocompletePluginOptions {
|
|
|
56
45
|
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
57
46
|
*/
|
|
58
47
|
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Product/editor surface where autocomplete runs (e.g. "comment", "chat").
|
|
50
|
+
* Added to analytics and UFO metadata for cross-surface reporting.
|
|
51
|
+
*/
|
|
52
|
+
surface?: string;
|
|
59
53
|
/**
|
|
60
54
|
* When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
|
|
61
55
|
* the network-based slow-lane backend. Defaults to false (network client).
|
|
@@ -3,6 +3,9 @@
|
|
|
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
|
export interface ScoringCandidate {
|
|
8
11
|
authorFreq: number;
|
|
@@ -29,6 +32,17 @@ export interface GrammarFilterMeta {
|
|
|
29
32
|
export declare const STAGE1_WEIGHT = 0.35;
|
|
30
33
|
export declare const STAGE2_WEIGHT = 0.65;
|
|
31
34
|
export declare const MIN_STAGE1_SCORE = 0.35;
|
|
35
|
+
export declare const isGrammarDataLoaded: () => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch the POS tag and grammar transition payloads from the CDN.
|
|
38
|
+
*
|
|
39
|
+
* Safe to call repeatedly: the in-flight promise is shared, and a failure is not
|
|
40
|
+
* cached so a later call can retry.
|
|
41
|
+
*/
|
|
42
|
+
export declare const loadGrammarDataAsync: (options?: {
|
|
43
|
+
isLocalLLM?: boolean;
|
|
44
|
+
surface?: string;
|
|
45
|
+
}) => Promise<void>;
|
|
32
46
|
export interface PipelineDebug {
|
|
33
47
|
final: number;
|
|
34
48
|
grammarRejected: string[];
|
|
@@ -81,7 +81,6 @@ export declare const incrementSessionFreq: (word: string) => void;
|
|
|
81
81
|
export declare const ingestDocumentPage: (pageContent: string | undefined) => void;
|
|
82
82
|
export declare const predict: (textBefore: string) => string | null;
|
|
83
83
|
export declare const loadVectorsAsync: (options?: {
|
|
84
|
-
getBinaryUrl?: () => Promise<string>;
|
|
85
84
|
isLocalLLM?: boolean;
|
|
86
85
|
surface?: string;
|
|
87
86
|
}) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"registry": "https://registry.npmjs.org/"
|
|
9
9
|
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"ak-postbuild": "ls -d dist/* | xargs -n 1 copyfiles -u 1 -V src/pm-plugins/data/*.json"
|
|
12
|
-
},
|
|
13
10
|
"atlassian": {
|
|
14
11
|
"team": "Editor: AI",
|
|
15
12
|
"singleton": true
|
|
@@ -30,7 +27,7 @@
|
|
|
30
27
|
"wink-nlp": "^2.4.0"
|
|
31
28
|
},
|
|
32
29
|
"peerDependencies": {
|
|
33
|
-
"@atlaskit/editor-common": "^118.
|
|
30
|
+
"@atlaskit/editor-common": "^118.2.0",
|
|
34
31
|
"@atlaskit/editor-plugin-analytics": "^14.0.0",
|
|
35
32
|
"react": "^18.2.0 || ^19.2.0"
|
|
36
33
|
},
|
package/src/analytics/ufo.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const EXPERIENCE_NAME = {
|
|
|
37
37
|
SLOW_LANE_FETCH: 'slow-lane-fetch',
|
|
38
38
|
LOAD_VOCABULARY: 'load-vocabulary',
|
|
39
39
|
LOAD_VECTORS: 'load-vectors',
|
|
40
|
+
LOAD_GRAMMAR: 'load-grammar',
|
|
40
41
|
} as const;
|
|
41
42
|
|
|
42
43
|
export type AutocompleteExperienceName = (typeof EXPERIENCE_NAME)[keyof typeof EXPERIENCE_NAME];
|
|
@@ -69,6 +70,10 @@ const experiences: Record<AutocompleteExperienceName, ConcurrentExperience> = {
|
|
|
69
70
|
EXPERIENCE_NAME.LOAD_VECTORS,
|
|
70
71
|
operationConfig,
|
|
71
72
|
),
|
|
73
|
+
[EXPERIENCE_NAME.LOAD_GRAMMAR]: new ConcurrentExperience(
|
|
74
|
+
EXPERIENCE_NAME.LOAD_GRAMMAR,
|
|
75
|
+
operationConfig,
|
|
76
|
+
),
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
export const startExp = (
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifacts Manifest Client: resolves the autocomplete model artifacts hosted on
|
|
3
|
+
* the CDN.
|
|
4
|
+
*
|
|
5
|
+
* `GET /gateway/api/v1/autocomplete/artifacts` returns one presigned CDN URL per
|
|
6
|
+
* artifact for the tenant's active model version:
|
|
7
|
+
*
|
|
8
|
+
* { activeVersion, artifacts: [{ name, url, contentType, sizeBytes, checksum }] }
|
|
9
|
+
*
|
|
10
|
+
* Every loader in the plugin resolves its payload through here, so no model data
|
|
11
|
+
* is bundled with the package.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
15
|
+
|
|
16
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manifest artifact names. These are the manifest's lookup keys and are
|
|
20
|
+
* hyphenated, unlike the underscored filenames the artifacts are generated from.
|
|
21
|
+
*/
|
|
22
|
+
export const ARTIFACT_NAME = {
|
|
23
|
+
FIRST_TOKEN_TO_WORDS: 'first-token-to-words.json',
|
|
24
|
+
GHOST_POS_TAGS: 'ghost-pos-tags.json',
|
|
25
|
+
GRAMMAR_TRANSITIONS: 'grammar-transitions-10k.json',
|
|
26
|
+
L3_VOCABULARY: 'l3-vocabulary.json',
|
|
27
|
+
POS_TAGS: 'combined-l2-l3-pos-tags.json',
|
|
28
|
+
VOCABULARY: 'vocabulary-10k.json',
|
|
29
|
+
WORD_INDEX: 'word-index-10k.json',
|
|
30
|
+
WORD_VECTORS: 'word-vectors-10k.bin',
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export type ArtifactName = (typeof ARTIFACT_NAME)[keyof typeof ARTIFACT_NAME];
|
|
34
|
+
|
|
35
|
+
/** A single entry in the artifacts manifest. */
|
|
36
|
+
export interface AutocompleteArtifact {
|
|
37
|
+
checksum: string;
|
|
38
|
+
contentType: string;
|
|
39
|
+
name: string;
|
|
40
|
+
sizeBytes: number;
|
|
41
|
+
url: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Response body of the artifacts endpoint. */
|
|
45
|
+
export interface AutocompleteArtifactsResponse {
|
|
46
|
+
activeVersion: string;
|
|
47
|
+
artifacts: AutocompleteArtifact[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const MANIFEST_ENDPOINT = '/gateway/api/v1/autocomplete/artifacts';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Presigned artifact URLs expire ~10 minutes after the manifest is issued, so
|
|
56
|
+
* the manifest is re-requested well inside that window instead of being cached
|
|
57
|
+
* for the session and handing out dead URLs to a late loader.
|
|
58
|
+
*/
|
|
59
|
+
const MANIFEST_TTL_MS = 5 * 60 * 1000;
|
|
60
|
+
|
|
61
|
+
// ─── Manifest ────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
let manifestPromise: Promise<Map<string, AutocompleteArtifact>> | undefined;
|
|
64
|
+
let manifestRequestedAt = 0;
|
|
65
|
+
|
|
66
|
+
/** Test-only: drops the memoised manifest so the next lookup re-requests it. */
|
|
67
|
+
export const resetArtifactsManifestCache = (): void => {
|
|
68
|
+
manifestPromise = undefined;
|
|
69
|
+
manifestRequestedAt = 0;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const requestManifest = async (): Promise<Map<string, AutocompleteArtifact>> => {
|
|
73
|
+
const res = await fetch(MANIFEST_ENDPOINT, {
|
|
74
|
+
method: 'GET',
|
|
75
|
+
headers: {
|
|
76
|
+
'x-experience-id': 'confluence-smart-typeahead-artifacts',
|
|
77
|
+
'x-product': 'confluence',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
throw new Error(`[autocomplete-artifacts] Manifest request failed: ${res.status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const body = (await res.json()) as AutocompleteArtifactsResponse;
|
|
86
|
+
|
|
87
|
+
if (!Array.isArray(body?.artifacts) || body.artifacts.length === 0) {
|
|
88
|
+
throw new Error('[autocomplete-artifacts] Manifest response contained no artifacts');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isAutocompleteDebugEnabled()) {
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log('[autocomplete-artifacts] Manifest loaded:', {
|
|
94
|
+
activeVersion: body.activeVersion,
|
|
95
|
+
names: body.artifacts.map((artifact) => artifact.name),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Map(body.artifacts.map((artifact) => [artifact.name, artifact]));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getManifest = (): Promise<Map<string, AutocompleteArtifact>> => {
|
|
103
|
+
if (manifestPromise && Date.now() - manifestRequestedAt < MANIFEST_TTL_MS) {
|
|
104
|
+
return manifestPromise;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
manifestRequestedAt = Date.now();
|
|
108
|
+
manifestPromise = requestManifest().catch((e) => {
|
|
109
|
+
// Never cache a rejection — a transient failure would otherwise stop every
|
|
110
|
+
// later loader in the session from retrying.
|
|
111
|
+
resetArtifactsManifestCache();
|
|
112
|
+
throw e;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return manifestPromise;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the CDN URL for an artifact.
|
|
122
|
+
*
|
|
123
|
+
* :raises Error: when the manifest cannot be fetched or omits the artifact.
|
|
124
|
+
*/
|
|
125
|
+
export const getArtifactUrl = async (name: ArtifactName): Promise<string> => {
|
|
126
|
+
const manifest = await getManifest();
|
|
127
|
+
const url = manifest.get(name)?.url;
|
|
128
|
+
|
|
129
|
+
if (!url) {
|
|
130
|
+
throw new Error(`[autocomplete-artifacts] Manifest has no URL for "${name}"`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return url;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Download and parse a JSON artifact. The presigned URL carries its own auth
|
|
138
|
+
* token, so the request is sent without Atlassian headers or credentials.
|
|
139
|
+
*
|
|
140
|
+
* :raises Error: when the manifest lookup or the download fails.
|
|
141
|
+
*/
|
|
142
|
+
export const fetchArtifactJson = async <T>(name: ArtifactName): Promise<T> => {
|
|
143
|
+
const url = await getArtifactUrl(name);
|
|
144
|
+
const res = await fetch(url);
|
|
145
|
+
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
throw new Error(`[autocomplete-artifacts] Failed to download "${name}": ${res.status}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (await res.json()) as T;
|
|
151
|
+
};
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type LocalSlowLaneLoadError,
|
|
23
23
|
type LocalSlowLaneLoadSuccess,
|
|
24
24
|
} from './local-slow-lane-client';
|
|
25
|
+
import { loadGrammarDataAsync } from './scoring-pipeline';
|
|
25
26
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
26
27
|
import {
|
|
27
28
|
predict,
|
|
@@ -224,22 +225,11 @@ export interface AutocompleteContext {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
export interface AutocompletePluginOptions {
|
|
227
|
-
/**
|
|
228
|
-
* Product/editor surface where autocomplete runs (e.g. "comment", "chat").
|
|
229
|
-
* Added to analytics and UFO metadata for cross-surface reporting.
|
|
230
|
-
*/
|
|
231
|
-
surface?: string;
|
|
232
228
|
/**
|
|
233
229
|
* Async function called once on first editor focus to retrieve context for
|
|
234
230
|
* word-frequency boosting. Called lazily so the preset can remain synchronous.
|
|
235
231
|
*/
|
|
236
232
|
getContext?: () => Promise<AutocompleteContext | undefined>;
|
|
237
|
-
/**
|
|
238
|
-
* Async function that resolves to a URL for the word vectors binary file.
|
|
239
|
-
* When provided, this takes precedence over the bundled asset URL.
|
|
240
|
-
* Use this to serve vectors from a CDN or media service in production.
|
|
241
|
-
*/
|
|
242
|
-
getVectorsBinaryUrl?: () => Promise<string>;
|
|
243
233
|
/**
|
|
244
234
|
* User locale used to determine whether autocomplete should run.
|
|
245
235
|
* Defaults to browser locale when omitted.
|
|
@@ -251,6 +241,11 @@ export interface AutocompletePluginOptions {
|
|
|
251
241
|
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
252
242
|
*/
|
|
253
243
|
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
244
|
+
/**
|
|
245
|
+
* Product/editor surface where autocomplete runs (e.g. "comment", "chat").
|
|
246
|
+
* Added to analytics and UFO metadata for cross-surface reporting.
|
|
247
|
+
*/
|
|
248
|
+
surface?: string;
|
|
254
249
|
/**
|
|
255
250
|
* When true, uses on-device inference via WebGPU (MLC WebLLM) instead of
|
|
256
251
|
* the network-based slow-lane backend. Defaults to false (network client).
|
|
@@ -831,7 +826,6 @@ export const createAutocompletePlugin = (
|
|
|
831
826
|
});
|
|
832
827
|
});
|
|
833
828
|
loadVectorsAsync({
|
|
834
|
-
getBinaryUrl: options?.getVectorsBinaryUrl,
|
|
835
829
|
isLocalLLM: options?.useLocalModel ?? false,
|
|
836
830
|
surface,
|
|
837
831
|
}).catch((error) => {
|
|
@@ -839,6 +833,14 @@ export const createAutocompletePlugin = (
|
|
|
839
833
|
location: 'editor-plugin-autocomplete/loadVectorsAsync',
|
|
840
834
|
});
|
|
841
835
|
});
|
|
836
|
+
loadGrammarDataAsync({
|
|
837
|
+
isLocalLLM: options?.useLocalModel ?? false,
|
|
838
|
+
surface,
|
|
839
|
+
}).catch((error) => {
|
|
840
|
+
logException(error as Error, {
|
|
841
|
+
location: 'editor-plugin-autocomplete/loadGrammarDataAsync',
|
|
842
|
+
});
|
|
843
|
+
});
|
|
842
844
|
if (!hasIngestedPage) {
|
|
843
845
|
hasIngestedPage = true;
|
|
844
846
|
refreshContext({
|