@atlaskit/editor-plugin-autocomplete 4.0.3 → 4.0.4
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 +15 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +22 -2
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +8 -8
- package/dist/cjs/pm-plugins/text-predictor.js +2 -2
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +68 -46
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +8 -6
- package/dist/es2019/pm-plugins/text-predictor.js +2 -2
- package/dist/esm/pm-plugins/autocomplete-plugin.js +22 -2
- package/dist/esm/pm-plugins/local-slow-lane-client.js +8 -8
- package/dist/esm/pm-plugins/text-predictor.js +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/pm-plugins/autocomplete-plugin.d.ts +6 -0
- package/package.json +2 -2
- package/src/index.ts +4 -0
- package/src/pm-plugins/autocomplete-plugin.ts +82 -54
- package/src/pm-plugins/local-slow-lane-client.ts +6 -5
- package/src/pm-plugins/text-predictor.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-autocomplete
|
|
2
2
|
|
|
3
|
+
## 4.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ac12ed0b41ef4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ac12ed0b41ef4) -
|
|
8
|
+
Refactor contextual autocomplete wiring so the conversation-store-aware ChatInput owns the
|
|
9
|
+
autocomplete context bridge (getContext + subscribeToContextUpdates) via a new
|
|
10
|
+
useAutocompleteEditorContext hook, and the shared RovoChatPromptInput simply forwards a single
|
|
11
|
+
`autocomplete` prop to the editor preset. Removes the duplicated keying / listener-set plumbing
|
|
12
|
+
from the reusable input.
|
|
13
|
+
|
|
14
|
+
Adds an optional `subscribeToContextUpdates` option to the autocomplete plugin so host surfaces
|
|
15
|
+
that stream context after mount (e.g. Rovo chat messages) can push refreshes, complementing the
|
|
16
|
+
existing word-boundary retry that only covers a one-time, still-loading comment thread.
|
|
17
|
+
|
|
3
18
|
## 4.0.3
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -249,6 +249,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
249
249
|
* user has already typed several words before the promise resolved.
|
|
250
250
|
*/
|
|
251
251
|
var currentView = null;
|
|
252
|
+
var unsubscribeFromContextUpdates;
|
|
252
253
|
/**
|
|
253
254
|
* Set after accepting a suggestion so the next doc-change update
|
|
254
255
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -670,7 +671,21 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
670
671
|
}
|
|
671
672
|
}
|
|
672
673
|
},
|
|
673
|
-
view: function view() {
|
|
674
|
+
view: function view(editorView) {
|
|
675
|
+
// Capture up front so a subscription notification before the first PM
|
|
676
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
677
|
+
currentView = editorView;
|
|
678
|
+
|
|
679
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
680
|
+
if (isAutocompleteEnabled && options !== null && options !== void 0 && options.subscribeToContextUpdates) {
|
|
681
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(function () {
|
|
682
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
683
|
+
refreshContext({
|
|
684
|
+
source: 'subscription',
|
|
685
|
+
allowThrottle: false
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
}
|
|
674
689
|
return {
|
|
675
690
|
update: function update(view, prevState) {
|
|
676
691
|
if (!isAutocompleteEnabled) {
|
|
@@ -699,7 +714,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
699
714
|
// thread still loading). Retry on word boundaries until we have
|
|
700
715
|
// the parent comment, throttled so we don't refetch constantly
|
|
701
716
|
// and capped so non-comment editors stop retrying entirely.
|
|
702
|
-
|
|
717
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
718
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
719
|
+
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((_resolvedContext = resolvedContext) !== null && _resolvedContext !== void 0 && _resolvedContext.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
703
720
|
// Only count the attempt when a fetch actually started, so an
|
|
704
721
|
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
705
722
|
if (refreshContext({
|
|
@@ -713,8 +730,11 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
713
730
|
}
|
|
714
731
|
},
|
|
715
732
|
destroy: function destroy() {
|
|
733
|
+
var _unsubscribeFromConte;
|
|
716
734
|
destroyed = true;
|
|
717
735
|
currentView = null;
|
|
736
|
+
(_unsubscribeFromConte = unsubscribeFromContextUpdates) === null || _unsubscribeFromConte === void 0 || _unsubscribeFromConte();
|
|
737
|
+
unsubscribeFromContextUpdates = undefined;
|
|
718
738
|
if (debounceTimer) {
|
|
719
739
|
clearTimeout(debounceTimer);
|
|
720
740
|
}
|
|
@@ -264,7 +264,7 @@ var bePayloadDataPromise;
|
|
|
264
264
|
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
265
265
|
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
266
266
|
*/
|
|
267
|
-
|
|
267
|
+
function unwrapJsonModule(mod, shape) {
|
|
268
268
|
if (mod == null || (0, _typeof2.default)(mod) !== 'object') {
|
|
269
269
|
return null;
|
|
270
270
|
}
|
|
@@ -316,7 +316,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
316
316
|
return namespace.default;
|
|
317
317
|
}
|
|
318
318
|
return null;
|
|
319
|
-
}
|
|
319
|
+
}
|
|
320
320
|
|
|
321
321
|
/**
|
|
322
322
|
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
@@ -964,6 +964,12 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
|
|
|
964
964
|
return _regenerator.default.wrap(function (_context4) {
|
|
965
965
|
while (1) switch (_context4.prev = _context4.next) {
|
|
966
966
|
case 0:
|
|
967
|
+
captureCompletionTime = function _captureCompletionTim(promise, onResolved) {
|
|
968
|
+
return promise.then(function (value) {
|
|
969
|
+
onResolved(performance.now());
|
|
970
|
+
return value;
|
|
971
|
+
});
|
|
972
|
+
};
|
|
967
973
|
if (!(!engine || destroyed)) {
|
|
968
974
|
_context4.next = 1;
|
|
969
975
|
break;
|
|
@@ -983,12 +989,6 @@ var createLocalSlowLaneClient = exports.createLocalSlowLaneClient = function cre
|
|
|
983
989
|
lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
|
|
984
990
|
semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
|
|
985
991
|
arcticInput = wrapForArctic(semanticText);
|
|
986
|
-
captureCompletionTime = function captureCompletionTime(promise, onResolved) {
|
|
987
|
-
return promise.then(function (value) {
|
|
988
|
-
onResolved(performance.now());
|
|
989
|
-
return value;
|
|
990
|
-
});
|
|
991
|
-
};
|
|
992
992
|
if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
|
|
993
993
|
// eslint-disable-next-line no-console
|
|
994
994
|
console.log("%c[LocalSlowLane] %c\uD83D\uDD22 Arctic input (".concat(arcticInput.length, " chars, ").concat(splitOnWhitespace(semanticText).length, " words): \"").concat(arcticInput.length > 100 ? "".concat(arcticInput.slice(0, 100), "\u2026") : arcticInput, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
|
|
@@ -694,7 +694,7 @@ var predict = exports.predict = function predict(textBefore) {
|
|
|
694
694
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
695
695
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
696
696
|
*/
|
|
697
|
-
|
|
697
|
+
function unwrapJsonModule(mod, shape) {
|
|
698
698
|
if (mod == null || (0, _typeof2.default)(mod) !== 'object') {
|
|
699
699
|
return null;
|
|
700
700
|
}
|
|
@@ -730,7 +730,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
730
730
|
return namespace.default;
|
|
731
731
|
}
|
|
732
732
|
return null;
|
|
733
|
-
}
|
|
733
|
+
}
|
|
734
734
|
var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
735
735
|
var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(options) {
|
|
736
736
|
var _options$isLocalLLM;
|
|
@@ -237,6 +237,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
237
237
|
* user has already typed several words before the promise resolved.
|
|
238
238
|
*/
|
|
239
239
|
let currentView = null;
|
|
240
|
+
let unsubscribeFromContextUpdates;
|
|
240
241
|
/**
|
|
241
242
|
* Set after accepting a suggestion so the next doc-change update
|
|
242
243
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -657,59 +658,80 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
657
658
|
}
|
|
658
659
|
}
|
|
659
660
|
},
|
|
660
|
-
view:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
currentView = view;
|
|
666
|
-
if (!prevState.doc.eq(view.state.doc)) {
|
|
667
|
-
if (justAccepted) {
|
|
668
|
-
justAccepted = false;
|
|
661
|
+
view: editorView => {
|
|
662
|
+
// Capture up front so a subscription notification before the first PM
|
|
663
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
664
|
+
currentView = editorView;
|
|
669
665
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
666
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
667
|
+
if (isAutocompleteEnabled && options !== null && options !== void 0 && options.subscribeToContextUpdates) {
|
|
668
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(() => {
|
|
669
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
670
|
+
refreshContext({
|
|
671
|
+
source: 'subscription',
|
|
672
|
+
allowThrottle: false
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
update: (view, prevState) => {
|
|
678
|
+
if (!isAutocompleteEnabled) {
|
|
676
679
|
return;
|
|
677
680
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
681
|
+
currentView = view;
|
|
682
|
+
if (!prevState.doc.eq(view.state.doc)) {
|
|
683
|
+
if (justAccepted) {
|
|
684
|
+
justAccepted = false;
|
|
685
|
+
|
|
686
|
+
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
687
|
+
// the dismissedContext guard and abort until the user types again.
|
|
688
|
+
dismissedContext = getTextBeforeCursor(view.state);
|
|
689
|
+
if (debounceTimer) {
|
|
690
|
+
clearTimeout(debounceTimer);
|
|
691
|
+
}
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
maybeUpdateSessionFrequency(view, prevState);
|
|
695
|
+
const textBefore = getTextBeforeCursor(view.state);
|
|
696
|
+
if (isWordBoundary(textBefore)) {
|
|
697
|
+
var _resolvedContext;
|
|
698
|
+
slowLaneClient.updateContext(buildSlowLaneText(view.state.doc.textContent, resolvedContext));
|
|
683
699
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
700
|
+
// Context may not have resolved on first focus (e.g. comment
|
|
701
|
+
// thread still loading). Retry on word boundaries until we have
|
|
702
|
+
// the parent comment, throttled so we don't refetch constantly
|
|
703
|
+
// and capped so non-comment editors stop retrying entirely.
|
|
704
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
705
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
706
|
+
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((_resolvedContext = resolvedContext) !== null && _resolvedContext !== void 0 && _resolvedContext.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
707
|
+
// Only count the attempt when a fetch actually started, so an
|
|
708
|
+
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
709
|
+
if (refreshContext({
|
|
710
|
+
source: 'word-boundary'
|
|
711
|
+
})) {
|
|
712
|
+
wordBoundaryRefreshAttempts++;
|
|
713
|
+
}
|
|
695
714
|
}
|
|
696
715
|
}
|
|
716
|
+
schedulePrediction(view);
|
|
697
717
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
slowLaneClient
|
|
718
|
+
},
|
|
719
|
+
destroy: () => {
|
|
720
|
+
var _unsubscribeFromConte;
|
|
721
|
+
destroyed = true;
|
|
722
|
+
currentView = null;
|
|
723
|
+
(_unsubscribeFromConte = unsubscribeFromContextUpdates) === null || _unsubscribeFromConte === void 0 ? void 0 : _unsubscribeFromConte();
|
|
724
|
+
unsubscribeFromContextUpdates = undefined;
|
|
725
|
+
if (debounceTimer) {
|
|
726
|
+
clearTimeout(debounceTimer);
|
|
727
|
+
}
|
|
728
|
+
if (hasDestroy(slowLaneClient)) {
|
|
729
|
+
slowLaneClient.destroy();
|
|
730
|
+
}
|
|
731
|
+
ingestedContextTexts.clear();
|
|
732
|
+
setDefaultSlowLaneClient(null);
|
|
709
733
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
})
|
|
734
|
+
};
|
|
735
|
+
}
|
|
714
736
|
});
|
|
715
737
|
};
|
|
@@ -247,7 +247,7 @@ let bePayloadDataPromise;
|
|
|
247
247
|
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
248
248
|
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
249
249
|
*/
|
|
250
|
-
|
|
250
|
+
function unwrapJsonModule(mod, shape) {
|
|
251
251
|
if (mod == null || typeof mod !== 'object') {
|
|
252
252
|
return null;
|
|
253
253
|
}
|
|
@@ -288,7 +288,7 @@ const unwrapJsonModule = (mod, shape) => {
|
|
|
288
288
|
return namespace.default;
|
|
289
289
|
}
|
|
290
290
|
return null;
|
|
291
|
-
}
|
|
291
|
+
}
|
|
292
292
|
|
|
293
293
|
/**
|
|
294
294
|
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
@@ -764,10 +764,12 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
764
764
|
const lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
|
|
765
765
|
const semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
|
|
766
766
|
const arcticInput = wrapForArctic(semanticText);
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
767
|
+
function captureCompletionTime(promise, onResolved) {
|
|
768
|
+
return promise.then(value => {
|
|
769
|
+
onResolved(performance.now());
|
|
770
|
+
return value;
|
|
771
|
+
});
|
|
772
|
+
}
|
|
771
773
|
if (isAutocompleteDebugEnabled()) {
|
|
772
774
|
// eslint-disable-next-line no-console
|
|
773
775
|
console.log(`%c[LocalSlowLane] %c🔢 Arctic input (${arcticInput.length} chars, ${splitOnWhitespace(semanticText).length} words): "${arcticInput.length > 100 ? `${arcticInput.slice(0, 100)}…` : arcticInput}"`, 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
|
|
@@ -560,7 +560,7 @@ export const predict = textBefore => {
|
|
|
560
560
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
561
561
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
562
562
|
*/
|
|
563
|
-
|
|
563
|
+
function unwrapJsonModule(mod, shape) {
|
|
564
564
|
if (mod == null || typeof mod !== 'object') {
|
|
565
565
|
return null;
|
|
566
566
|
}
|
|
@@ -585,7 +585,7 @@ const unwrapJsonModule = (mod, shape) => {
|
|
|
585
585
|
return namespace.default;
|
|
586
586
|
}
|
|
587
587
|
return null;
|
|
588
|
-
}
|
|
588
|
+
}
|
|
589
589
|
export const loadVectorsAsync = async options => {
|
|
590
590
|
var _options$isLocalLLM;
|
|
591
591
|
if (vectorStore || vectorsLoadStarted) {
|
|
@@ -242,6 +242,7 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
242
242
|
* user has already typed several words before the promise resolved.
|
|
243
243
|
*/
|
|
244
244
|
var currentView = null;
|
|
245
|
+
var unsubscribeFromContextUpdates;
|
|
245
246
|
/**
|
|
246
247
|
* Set after accepting a suggestion so the next doc-change update
|
|
247
248
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -663,7 +664,21 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
663
664
|
}
|
|
664
665
|
}
|
|
665
666
|
},
|
|
666
|
-
view: function view() {
|
|
667
|
+
view: function view(editorView) {
|
|
668
|
+
// Capture up front so a subscription notification before the first PM
|
|
669
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
670
|
+
currentView = editorView;
|
|
671
|
+
|
|
672
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
673
|
+
if (isAutocompleteEnabled && options !== null && options !== void 0 && options.subscribeToContextUpdates) {
|
|
674
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(function () {
|
|
675
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
676
|
+
refreshContext({
|
|
677
|
+
source: 'subscription',
|
|
678
|
+
allowThrottle: false
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
667
682
|
return {
|
|
668
683
|
update: function update(view, prevState) {
|
|
669
684
|
if (!isAutocompleteEnabled) {
|
|
@@ -692,7 +707,9 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
692
707
|
// thread still loading). Retry on word boundaries until we have
|
|
693
708
|
// the parent comment, throttled so we don't refetch constantly
|
|
694
709
|
// and capped so non-comment editors stop retrying entirely.
|
|
695
|
-
|
|
710
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
711
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
712
|
+
if (!(options !== null && options !== void 0 && options.subscribeToContextUpdates) && !((_resolvedContext = resolvedContext) !== null && _resolvedContext !== void 0 && _resolvedContext.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
696
713
|
// Only count the attempt when a fetch actually started, so an
|
|
697
714
|
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
698
715
|
if (refreshContext({
|
|
@@ -706,8 +723,11 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
|
|
|
706
723
|
}
|
|
707
724
|
},
|
|
708
725
|
destroy: function destroy() {
|
|
726
|
+
var _unsubscribeFromConte;
|
|
709
727
|
destroyed = true;
|
|
710
728
|
currentView = null;
|
|
729
|
+
(_unsubscribeFromConte = unsubscribeFromContextUpdates) === null || _unsubscribeFromConte === void 0 || _unsubscribeFromConte();
|
|
730
|
+
unsubscribeFromContextUpdates = undefined;
|
|
711
731
|
if (debounceTimer) {
|
|
712
732
|
clearTimeout(debounceTimer);
|
|
713
733
|
}
|
|
@@ -258,7 +258,7 @@ var bePayloadDataPromise;
|
|
|
258
258
|
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
259
259
|
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
260
260
|
*/
|
|
261
|
-
|
|
261
|
+
function unwrapJsonModule(mod, shape) {
|
|
262
262
|
if (mod == null || _typeof(mod) !== 'object') {
|
|
263
263
|
return null;
|
|
264
264
|
}
|
|
@@ -310,7 +310,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
310
310
|
return namespace.default;
|
|
311
311
|
}
|
|
312
312
|
return null;
|
|
313
|
-
}
|
|
313
|
+
}
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
316
|
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
@@ -952,6 +952,12 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
952
952
|
return _regeneratorRuntime.wrap(function (_context4) {
|
|
953
953
|
while (1) switch (_context4.prev = _context4.next) {
|
|
954
954
|
case 0:
|
|
955
|
+
captureCompletionTime = function _captureCompletionTim(promise, onResolved) {
|
|
956
|
+
return promise.then(function (value) {
|
|
957
|
+
onResolved(performance.now());
|
|
958
|
+
return value;
|
|
959
|
+
});
|
|
960
|
+
};
|
|
955
961
|
if (!(!engine || destroyed)) {
|
|
956
962
|
_context4.next = 1;
|
|
957
963
|
break;
|
|
@@ -971,12 +977,6 @@ export var createLocalSlowLaneClient = function createLocalSlowLaneClient() {
|
|
|
971
977
|
lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
|
|
972
978
|
semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
|
|
973
979
|
arcticInput = wrapForArctic(semanticText);
|
|
974
|
-
captureCompletionTime = function captureCompletionTime(promise, onResolved) {
|
|
975
|
-
return promise.then(function (value) {
|
|
976
|
-
onResolved(performance.now());
|
|
977
|
-
return value;
|
|
978
|
-
});
|
|
979
|
-
};
|
|
980
980
|
if (isAutocompleteDebugEnabled()) {
|
|
981
981
|
// eslint-disable-next-line no-console
|
|
982
982
|
console.log("%c[LocalSlowLane] %c\uD83D\uDD22 Arctic input (".concat(arcticInput.length, " chars, ").concat(splitOnWhitespace(semanticText).length, " words): \"").concat(arcticInput.length > 100 ? "".concat(arcticInput.slice(0, 100), "\u2026") : arcticInput, "\""), 'color: #9c27b0; font-weight: bold;', 'color: #009688;');
|
|
@@ -690,7 +690,7 @@ export var predict = function predict(textBefore) {
|
|
|
690
690
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
691
691
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
692
692
|
*/
|
|
693
|
-
|
|
693
|
+
function unwrapJsonModule(mod, shape) {
|
|
694
694
|
if (mod == null || _typeof(mod) !== 'object') {
|
|
695
695
|
return null;
|
|
696
696
|
}
|
|
@@ -726,7 +726,7 @@ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
|
726
726
|
return namespace.default;
|
|
727
727
|
}
|
|
728
728
|
return null;
|
|
729
|
-
}
|
|
729
|
+
}
|
|
730
730
|
export var loadVectorsAsync = /*#__PURE__*/function () {
|
|
731
731
|
var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
|
|
732
732
|
var _options$isLocalLLM;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface AutocompletePluginOptions {
|
|
|
40
40
|
* Use this to serve vectors from a CDN or media service in production.
|
|
41
41
|
*/
|
|
42
42
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Subscription for hosts with live external context (e.g. Rovo chat). The plugin
|
|
45
|
+
* re-fetches via getContext() on each notification and unsubscribes on destroy.
|
|
46
|
+
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
47
|
+
*/
|
|
48
|
+
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
43
49
|
/**
|
|
44
50
|
* User locale used to determine whether autocomplete should run.
|
|
45
51
|
* Defaults to browser locale when omitted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-autocomplete",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"wink-nlp": "^2.4.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@atlaskit/editor-common": "^116.
|
|
30
|
+
"@atlaskit/editor-common": "^116.15.0",
|
|
31
31
|
"@atlaskit/editor-plugin-analytics": "^12.0.0",
|
|
32
32
|
"react": "^18.2.0"
|
|
33
33
|
},
|
package/src/index.ts
CHANGED
|
@@ -214,6 +214,12 @@ export interface AutocompletePluginOptions {
|
|
|
214
214
|
* Use this to serve vectors from a CDN or media service in production.
|
|
215
215
|
*/
|
|
216
216
|
getVectorsBinaryUrl?: () => Promise<string>;
|
|
217
|
+
/**
|
|
218
|
+
* Subscription for hosts with live external context (e.g. Rovo chat). The plugin
|
|
219
|
+
* re-fetches via getContext() on each notification and unsubscribes on destroy.
|
|
220
|
+
* Only meaningful alongside `getContext`; without it each notification is a no-op.
|
|
221
|
+
*/
|
|
222
|
+
subscribeToContextUpdates?: (onContextUpdated: () => void) => () => void;
|
|
217
223
|
/**
|
|
218
224
|
* User locale used to determine whether autocomplete should run.
|
|
219
225
|
* Defaults to browser locale when omitted.
|
|
@@ -329,6 +335,7 @@ export const createAutocompletePlugin = (
|
|
|
329
335
|
* user has already typed several words before the promise resolved.
|
|
330
336
|
*/
|
|
331
337
|
let currentView: EditorView | null = null;
|
|
338
|
+
let unsubscribeFromContextUpdates: (() => void) | undefined;
|
|
332
339
|
/**
|
|
333
340
|
* Set after accepting a suggestion so the next doc-change update
|
|
334
341
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -582,7 +589,9 @@ export const createAutocompletePlugin = (
|
|
|
582
589
|
// nodeAfter correctly handles inline atoms (mentions, emojis) where
|
|
583
590
|
// parentOffset and textContent indices diverge.
|
|
584
591
|
const nodeAfter = selection.$from.nodeAfter;
|
|
585
|
-
const charAfterCursor = nodeAfter?.isText
|
|
592
|
+
const charAfterCursor = nodeAfter?.isText
|
|
593
|
+
? getLeadingTextCharacter(nodeAfter.text)
|
|
594
|
+
: undefined;
|
|
586
595
|
// Only suppress for Unicode letters, digits, and underscore — hyphens,
|
|
587
596
|
// apostrophes, and similar punctuation are valid left-edge boundaries
|
|
588
597
|
// and should not block suggestions (e.g. cursor before '-' in compound-word).
|
|
@@ -790,66 +799,85 @@ export const createAutocompletePlugin = (
|
|
|
790
799
|
},
|
|
791
800
|
},
|
|
792
801
|
|
|
793
|
-
view: () =>
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
802
|
+
view: (editorView: EditorView) => {
|
|
803
|
+
// Capture up front so a subscription notification before the first PM
|
|
804
|
+
// transaction can still drive slowLaneClient.updateContext (gated on currentView).
|
|
805
|
+
currentView = editorView;
|
|
798
806
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
dismissedContext = getTextBeforeCursor(view.state);
|
|
807
|
+
// Push channel for hosts that keep producing context after mount (e.g. Rovo chat).
|
|
808
|
+
if (isAutocompleteEnabled && options?.subscribeToContextUpdates) {
|
|
809
|
+
unsubscribeFromContextUpdates = options.subscribeToContextUpdates(() => {
|
|
810
|
+
// Bypass throttle for freshness; the in-flight guard prevents overlap.
|
|
811
|
+
refreshContext({ source: 'subscription', allowThrottle: false });
|
|
812
|
+
});
|
|
813
|
+
}
|
|
807
814
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
815
|
+
return {
|
|
816
|
+
update: (view: EditorView, prevState: EditorState) => {
|
|
817
|
+
if (!isAutocompleteEnabled) {
|
|
811
818
|
return;
|
|
812
819
|
}
|
|
813
820
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
// and capped so non-comment editors stop retrying entirely.
|
|
826
|
-
if (
|
|
827
|
-
!resolvedContext?.parentCommentContent &&
|
|
828
|
-
wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS
|
|
829
|
-
) {
|
|
830
|
-
// Only count the attempt when a fetch actually started, so an
|
|
831
|
-
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
832
|
-
if (refreshContext({ source: 'word-boundary' })) {
|
|
833
|
-
wordBoundaryRefreshAttempts++;
|
|
821
|
+
currentView = view;
|
|
822
|
+
if (!prevState.doc.eq(view.state.doc)) {
|
|
823
|
+
if (justAccepted) {
|
|
824
|
+
justAccepted = false;
|
|
825
|
+
|
|
826
|
+
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
827
|
+
// the dismissedContext guard and abort until the user types again.
|
|
828
|
+
dismissedContext = getTextBeforeCursor(view.state);
|
|
829
|
+
|
|
830
|
+
if (debounceTimer) {
|
|
831
|
+
clearTimeout(debounceTimer);
|
|
834
832
|
}
|
|
833
|
+
return;
|
|
835
834
|
}
|
|
836
|
-
}
|
|
837
835
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
836
|
+
maybeUpdateSessionFrequency(view, prevState);
|
|
837
|
+
|
|
838
|
+
const textBefore = getTextBeforeCursor(view.state);
|
|
839
|
+
if (isWordBoundary(textBefore)) {
|
|
840
|
+
slowLaneClient.updateContext(
|
|
841
|
+
buildSlowLaneText(view.state.doc.textContent, resolvedContext),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
// Context may not have resolved on first focus (e.g. comment
|
|
845
|
+
// thread still loading). Retry on word boundaries until we have
|
|
846
|
+
// the parent comment, throttled so we don't refetch constantly
|
|
847
|
+
// and capped so non-comment editors stop retrying entirely.
|
|
848
|
+
// Skipped for push-channel hosts (subscribeToContextUpdates): they
|
|
849
|
+
// never grow parentCommentContent, so polling only burns the budget.
|
|
850
|
+
if (
|
|
851
|
+
!options?.subscribeToContextUpdates &&
|
|
852
|
+
!resolvedContext?.parentCommentContent &&
|
|
853
|
+
wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS
|
|
854
|
+
) {
|
|
855
|
+
// Only count the attempt when a fetch actually started, so an
|
|
856
|
+
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
857
|
+
if (refreshContext({ source: 'word-boundary' })) {
|
|
858
|
+
wordBoundaryRefreshAttempts++;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
schedulePrediction(view);
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
destroy: () => {
|
|
867
|
+
destroyed = true;
|
|
868
|
+
currentView = null;
|
|
869
|
+
unsubscribeFromContextUpdates?.();
|
|
870
|
+
unsubscribeFromContextUpdates = undefined;
|
|
871
|
+
if (debounceTimer) {
|
|
872
|
+
clearTimeout(debounceTimer);
|
|
873
|
+
}
|
|
874
|
+
if (hasDestroy(slowLaneClient)) {
|
|
875
|
+
slowLaneClient.destroy();
|
|
876
|
+
}
|
|
877
|
+
ingestedContextTexts.clear();
|
|
878
|
+
setDefaultSlowLaneClient(null);
|
|
879
|
+
},
|
|
880
|
+
};
|
|
881
|
+
},
|
|
854
882
|
});
|
|
855
883
|
};
|
|
@@ -369,7 +369,7 @@ let bePayloadDataPromise: Promise<void> | undefined;
|
|
|
369
369
|
* :param shape: `'object'` if the source JSON is `{...}`, `'array'` if `[...]`.
|
|
370
370
|
* :returns: The parsed JSON value, or `null` if neither interop mode applies.
|
|
371
371
|
*/
|
|
372
|
-
|
|
372
|
+
function unwrapJsonModule<T>(mod: unknown, shape: 'object' | 'array'): T | null {
|
|
373
373
|
if (mod == null || typeof mod !== 'object') {
|
|
374
374
|
return null;
|
|
375
375
|
}
|
|
@@ -411,7 +411,7 @@ const unwrapJsonModule = <T>(mod: unknown, shape: 'object' | 'array'): T | null
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
return null;
|
|
414
|
-
}
|
|
414
|
+
}
|
|
415
415
|
|
|
416
416
|
/**
|
|
417
417
|
* Lazily load and build the BE-parity lookup tables from their JSON payloads.
|
|
@@ -1004,14 +1004,15 @@ export const createLocalSlowLaneClient = (
|
|
|
1004
1004
|
const lmText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_TOKENS);
|
|
1005
1005
|
const semanticText = truncateToLastNWords(text, BE_PARITY.MAX_CONTEXT_WORDS);
|
|
1006
1006
|
const arcticInput = wrapForArctic(semanticText);
|
|
1007
|
-
|
|
1007
|
+
function captureCompletionTime<T>(
|
|
1008
1008
|
promise: Promise<T>,
|
|
1009
1009
|
onResolved: (resolvedAt: number) => void,
|
|
1010
|
-
): Promise<T>
|
|
1011
|
-
promise.then((value: T) => {
|
|
1010
|
+
): Promise<T> {
|
|
1011
|
+
return promise.then((value: T) => {
|
|
1012
1012
|
onResolved(performance.now());
|
|
1013
1013
|
return value;
|
|
1014
1014
|
});
|
|
1015
|
+
}
|
|
1015
1016
|
|
|
1016
1017
|
if (isAutocompleteDebugEnabled()) {
|
|
1017
1018
|
// eslint-disable-next-line no-console
|
|
@@ -715,7 +715,7 @@ interface VocabularyJson {
|
|
|
715
715
|
* array and a sparse numeric-keyed object are emitted identically as named
|
|
716
716
|
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
717
717
|
*/
|
|
718
|
-
|
|
718
|
+
function unwrapJsonModule<T>(mod: unknown, shape: 'object' | 'array'): T | null {
|
|
719
719
|
if (mod == null || typeof mod !== 'object') {
|
|
720
720
|
return null;
|
|
721
721
|
}
|
|
@@ -743,7 +743,7 @@ const unwrapJsonModule = <T>(mod: unknown, shape: 'object' | 'array'): T | null
|
|
|
743
743
|
}
|
|
744
744
|
|
|
745
745
|
return null;
|
|
746
|
-
}
|
|
746
|
+
}
|
|
747
747
|
|
|
748
748
|
export const loadVectorsAsync = async (options?: {
|
|
749
749
|
getBinaryUrl?: () => Promise<string>;
|