@atlaskit/editor-plugin-autocomplete 3.3.0 → 3.4.1
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 +16 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +180 -23
- package/dist/cjs/pm-plugins/local-slow-lane-client.js +67 -4
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +166 -23
- package/dist/es2019/pm-plugins/local-slow-lane-client.js +66 -3
- package/dist/esm/pm-plugins/autocomplete-plugin.js +180 -23
- package/dist/esm/pm-plugins/local-slow-lane-client.js +67 -4
- package/package.json +2 -2
- package/src/pm-plugins/autocomplete-plugin.ts +185 -26
- package/src/pm-plugins/local-slow-lane-client.ts +72 -5
|
@@ -4,12 +4,26 @@ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
|
4
4
|
import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
|
|
5
5
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
6
6
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
7
|
+
import { isAutocompleteDebugEnabled } from './debug-mode';
|
|
7
8
|
import { createGhostTextDecorationSet } from './ghost-text-decoration';
|
|
8
9
|
import { createLocalSlowLaneClient } from './local-slow-lane-client';
|
|
9
10
|
import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
|
|
10
11
|
import { predict, loadDefaultVocabulary, loadVectorsAsync, incrementSessionFreq, ingestDocumentPage } from './text-predictor';
|
|
11
12
|
export const autocompletePluginKey = new PluginKey('autocomplete');
|
|
12
13
|
const DEBOUNCE_MS = 150;
|
|
14
|
+
const NETWORK_SLOW_LANE_DEBOUNCE_MS = 300;
|
|
15
|
+
const LOCAL_SLOW_LANE_DEBOUNCE_MS = 100;
|
|
16
|
+
const CONTEXT_REFRESH_THROTTLE_MS = 1000;
|
|
17
|
+
// Caps the dedup Set so long editing sessions don't retain every distinct
|
|
18
|
+
// version of the (potentially hundreds-of-KB) page content for the plugin's
|
|
19
|
+
// lifetime. Eviction is FIFO; a re-ingest of an evicted text is harmless.
|
|
20
|
+
const MAX_INGESTED_CONTEXT_TEXTS = 50;
|
|
21
|
+
// Caps how many times the word-boundary path will retry getContext() while the
|
|
22
|
+
// parent comment is still missing. Combined with the 1s throttle this gives a
|
|
23
|
+
// ~5s window to cover a still-loading comment thread, then stops permanently so
|
|
24
|
+
// non-comment editors (where parentCommentContent never arrives) don't refetch
|
|
25
|
+
// on every word boundary for the plugin's lifetime.
|
|
26
|
+
const MAX_CONTEXT_REFRESH_ATTEMPTS = 5;
|
|
13
27
|
const hasDestroy = client => 'destroy' in client && typeof client.destroy === 'function';
|
|
14
28
|
const createInitialState = () => ({
|
|
15
29
|
ghostText: '',
|
|
@@ -176,6 +190,12 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
176
190
|
let debounceTimer = null;
|
|
177
191
|
let hasIngestedPage = false;
|
|
178
192
|
let resolvedContext;
|
|
193
|
+
/**
|
|
194
|
+
* Kept in sync with the live EditorView so the async getContext() promise
|
|
195
|
+
* can re-trigger a slow-lane update the moment context arrives, even if the
|
|
196
|
+
* user has already typed several words before the promise resolved.
|
|
197
|
+
*/
|
|
198
|
+
let currentView = null;
|
|
179
199
|
/**
|
|
180
200
|
* Set after accepting a suggestion so the next doc-change update
|
|
181
201
|
* skips scheduling a new prediction for the just-inserted text.
|
|
@@ -191,6 +211,18 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
191
211
|
let dismissedContext = null;
|
|
192
212
|
let lastSuggestionTypedLength = 0;
|
|
193
213
|
let lastSuggestionLength = 0;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* cold → no slow-lane vector received yet; frequency-only trie scoring
|
|
217
|
+
* server → server slow-lane API returned the context vector
|
|
218
|
+
* localLlm → on-device WebGPU/MLC model returned the context vector
|
|
219
|
+
*/
|
|
220
|
+
const getCompletionSource = () => {
|
|
221
|
+
if (!slowLaneClient.getContextVector()) {
|
|
222
|
+
return 'cold';
|
|
223
|
+
}
|
|
224
|
+
return options !== null && options !== void 0 && options.useLocalModel ? 'localLlm' : 'server';
|
|
225
|
+
};
|
|
194
226
|
const fireSuggestionDismissedAnalytics = reason => {
|
|
195
227
|
var _api$analytics;
|
|
196
228
|
api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
|
|
@@ -198,6 +230,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
198
230
|
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
199
231
|
eventType: EVENT_TYPE.TRACK,
|
|
200
232
|
attributes: {
|
|
233
|
+
completionSource: getCompletionSource(),
|
|
201
234
|
reason
|
|
202
235
|
}
|
|
203
236
|
});
|
|
@@ -212,6 +245,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
212
245
|
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
213
246
|
eventType: EVENT_TYPE.TRACK,
|
|
214
247
|
attributes: {
|
|
248
|
+
completionSource: getCompletionSource(),
|
|
215
249
|
suggestionLength,
|
|
216
250
|
typedLength,
|
|
217
251
|
kssDelta
|
|
@@ -219,12 +253,115 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
219
253
|
});
|
|
220
254
|
};
|
|
221
255
|
const slowLaneClient = options !== null && options !== void 0 && options.useLocalModel ? createLocalSlowLaneClient({
|
|
222
|
-
debounceMs:
|
|
256
|
+
debounceMs: LOCAL_SLOW_LANE_DEBOUNCE_MS
|
|
223
257
|
}) : createSlowLaneClient({
|
|
224
258
|
baseUrl: '',
|
|
225
|
-
debounceMs:
|
|
259
|
+
debounceMs: NETWORK_SLOW_LANE_DEBOUNCE_MS
|
|
226
260
|
});
|
|
227
261
|
setDefaultSlowLaneClient(slowLaneClient);
|
|
262
|
+
let contextRequestInFlight = false;
|
|
263
|
+
let lastContextRefreshAt = 0;
|
|
264
|
+
// Bounds the word-boundary retry loop so it terminates even when the editor is
|
|
265
|
+
// not in a comment thread (parentCommentContent never resolves).
|
|
266
|
+
let wordBoundaryRefreshAttempts = 0;
|
|
267
|
+
// Set when the plugin is torn down so in-flight getContext() resolutions don't
|
|
268
|
+
// mutate the global text-predictor state after destruction.
|
|
269
|
+
let destroyed = false;
|
|
270
|
+
const ingestedContextTexts = new Set();
|
|
271
|
+
const logContextResolved = (source, context) => {
|
|
272
|
+
var _context$parentCommen, _context$siblingComme4, _context$siblingComme5;
|
|
273
|
+
if (!isAutocompleteDebugEnabled()) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// eslint-disable-next-line no-console
|
|
278
|
+
console.log('%c[Autocomplete] %cgetContext resolved', 'color: #00b8d9; font-weight: bold;', 'color: inherit;', {
|
|
279
|
+
source,
|
|
280
|
+
hasParentComment: !!(context !== null && context !== void 0 && context.parentCommentContent),
|
|
281
|
+
parentCommentPreview: context === null || context === void 0 ? void 0 : (_context$parentCommen = context.parentCommentContent) === null || _context$parentCommen === void 0 ? void 0 : _context$parentCommen.slice(0, 80),
|
|
282
|
+
siblingCount: (_context$siblingComme4 = context === null || context === void 0 ? void 0 : (_context$siblingComme5 = context.siblingCommentsContents) === null || _context$siblingComme5 === void 0 ? void 0 : _context$siblingComme5.length) !== null && _context$siblingComme4 !== void 0 ? _context$siblingComme4 : 0,
|
|
283
|
+
hasFullPage: !!(context !== null && context !== void 0 && context.fullPageContent)
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const applyContext = context => {
|
|
287
|
+
// Merge rather than replace: the word-boundary retry may resolve only a
|
|
288
|
+
// late-arriving field (e.g. parentCommentContent) without re-sending
|
|
289
|
+
// fullPageContent, so replacing would drop previously resolved context.
|
|
290
|
+
// Strip undefined values first so a field explicitly set to undefined by
|
|
291
|
+
// getContext doesn't overwrite a previously resolved value.
|
|
292
|
+
const definedContext = Object.fromEntries(Object.entries(context).filter(([, value]) => value !== undefined));
|
|
293
|
+
resolvedContext = {
|
|
294
|
+
...resolvedContext,
|
|
295
|
+
...definedContext
|
|
296
|
+
};
|
|
297
|
+
const ingestContextText = text => {
|
|
298
|
+
if (!text || ingestedContextTexts.has(text)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
ingestedContextTexts.add(text);
|
|
302
|
+
// Evict oldest entries (Set preserves insertion order) to bound memory.
|
|
303
|
+
while (ingestedContextTexts.size > MAX_INGESTED_CONTEXT_TEXTS) {
|
|
304
|
+
const oldest = ingestedContextTexts.values().next().value;
|
|
305
|
+
if (oldest === undefined) {
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
ingestedContextTexts.delete(oldest);
|
|
309
|
+
}
|
|
310
|
+
ingestDocumentPage(text);
|
|
311
|
+
};
|
|
312
|
+
ingestContextText(context.fullPageContent);
|
|
313
|
+
ingestContextText(context.parentCommentContent);
|
|
314
|
+
for (const siblingCommentContent of (_context$siblingComme6 = context.siblingCommentsContents) !== null && _context$siblingComme6 !== void 0 ? _context$siblingComme6 : []) {
|
|
315
|
+
var _context$siblingComme6;
|
|
316
|
+
ingestContextText(siblingCommentContent);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Context arrived after word boundaries may already have fired. Re-send
|
|
320
|
+
// slow-lane context immediately so the next inference includes the thread.
|
|
321
|
+
if (currentView) {
|
|
322
|
+
slowLaneClient.updateContext(buildSlowLaneText(currentView.state.doc.textContent, resolvedContext));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Returns true when a fetch was actually started, false when it was skipped
|
|
328
|
+
* (no getContext, a request already in flight, or throttled). Callers that
|
|
329
|
+
* track a retry budget should only count attempts where this returns true.
|
|
330
|
+
*/
|
|
331
|
+
const refreshContext = ({
|
|
332
|
+
source,
|
|
333
|
+
allowThrottle = true
|
|
334
|
+
}) => {
|
|
335
|
+
if (!(options !== null && options !== void 0 && options.getContext) || contextRequestInFlight) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
if (allowThrottle && now - lastContextRefreshAt < CONTEXT_REFRESH_THROTTLE_MS) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
contextRequestInFlight = true;
|
|
343
|
+
lastContextRefreshAt = now;
|
|
344
|
+
options.getContext().then(context => {
|
|
345
|
+
// Bail if the plugin was destroyed while the fetch was in flight —
|
|
346
|
+
// applyContext mutates global text-predictor state we must not touch
|
|
347
|
+
// after teardown.
|
|
348
|
+
if (destroyed) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
logContextResolved(source, context);
|
|
352
|
+
if (!context) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
applyContext(context);
|
|
356
|
+
}).catch(error => {
|
|
357
|
+
logException(error, {
|
|
358
|
+
location: 'editor-plugin-autocomplete/getContext'
|
|
359
|
+
});
|
|
360
|
+
}).finally(() => {
|
|
361
|
+
contextRequestInFlight = false;
|
|
362
|
+
});
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
228
365
|
|
|
229
366
|
/**
|
|
230
367
|
* Schedule a prediction after a short debounce.
|
|
@@ -269,7 +406,10 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
269
406
|
api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.fireAnalyticsEvent({
|
|
270
407
|
action: ACTION.SUGGESTION_VIEWED,
|
|
271
408
|
actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
|
|
272
|
-
eventType: EVENT_TYPE.TRACK
|
|
409
|
+
eventType: EVENT_TYPE.TRACK,
|
|
410
|
+
attributes: {
|
|
411
|
+
completionSource: getCompletionSource()
|
|
412
|
+
}
|
|
273
413
|
});
|
|
274
414
|
}
|
|
275
415
|
}
|
|
@@ -403,26 +543,10 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
403
543
|
});
|
|
404
544
|
if (!hasIngestedPage) {
|
|
405
545
|
hasIngestedPage = true;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
resolvedContext = context;
|
|
413
|
-
if (context.fullPageContent) {
|
|
414
|
-
ingestDocumentPage(context.fullPageContent);
|
|
415
|
-
}
|
|
416
|
-
if (context.parentCommentContent) {
|
|
417
|
-
ingestDocumentPage(context.parentCommentContent);
|
|
418
|
-
}
|
|
419
|
-
(_context$siblingComme4 = context.siblingCommentsContents) === null || _context$siblingComme4 === void 0 ? void 0 : _context$siblingComme4.forEach(ingestDocumentPage);
|
|
420
|
-
}).catch(error => {
|
|
421
|
-
logException(error, {
|
|
422
|
-
location: 'editor-plugin-autocomplete/getContext'
|
|
423
|
-
});
|
|
424
|
-
});
|
|
425
|
-
}
|
|
546
|
+
refreshContext({
|
|
547
|
+
source: 'focus',
|
|
548
|
+
allowThrottle: false
|
|
549
|
+
});
|
|
426
550
|
}
|
|
427
551
|
return false;
|
|
428
552
|
}
|
|
@@ -430,6 +554,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
430
554
|
},
|
|
431
555
|
view: () => ({
|
|
432
556
|
update: (view, prevState) => {
|
|
557
|
+
currentView = view;
|
|
433
558
|
if (!prevState.doc.eq(view.state.doc)) {
|
|
434
559
|
if (justAccepted) {
|
|
435
560
|
justAccepted = false;
|
|
@@ -445,18 +570,36 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
445
570
|
maybeUpdateSessionFrequency(view, prevState);
|
|
446
571
|
const textBefore = getTextBeforeCursor(view.state);
|
|
447
572
|
if (isWordBoundary(textBefore)) {
|
|
573
|
+
var _resolvedContext;
|
|
448
574
|
slowLaneClient.updateContext(buildSlowLaneText(view.state.doc.textContent, resolvedContext));
|
|
575
|
+
|
|
576
|
+
// Context may not have resolved on first focus (e.g. comment
|
|
577
|
+
// thread still loading). Retry on word boundaries until we have
|
|
578
|
+
// the parent comment, throttled so we don't refetch constantly
|
|
579
|
+
// and capped so non-comment editors stop retrying entirely.
|
|
580
|
+
if (!((_resolvedContext = resolvedContext) !== null && _resolvedContext !== void 0 && _resolvedContext.parentCommentContent) && wordBoundaryRefreshAttempts < MAX_CONTEXT_REFRESH_ATTEMPTS) {
|
|
581
|
+
// Only count the attempt when a fetch actually started, so an
|
|
582
|
+
// in-flight or throttled no-op doesn't burn the retry budget.
|
|
583
|
+
if (refreshContext({
|
|
584
|
+
source: 'word-boundary'
|
|
585
|
+
})) {
|
|
586
|
+
wordBoundaryRefreshAttempts++;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
449
589
|
}
|
|
450
590
|
schedulePrediction(view);
|
|
451
591
|
}
|
|
452
592
|
},
|
|
453
593
|
destroy: () => {
|
|
594
|
+
destroyed = true;
|
|
595
|
+
currentView = null;
|
|
454
596
|
if (debounceTimer) {
|
|
455
597
|
clearTimeout(debounceTimer);
|
|
456
598
|
}
|
|
457
599
|
if (hasDestroy(slowLaneClient)) {
|
|
458
600
|
slowLaneClient.destroy();
|
|
459
601
|
}
|
|
602
|
+
ingestedContextTexts.clear();
|
|
460
603
|
setDefaultSlowLaneClient(null);
|
|
461
604
|
}
|
|
462
605
|
})
|
|
@@ -481,6 +481,14 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
481
481
|
let lastRequestedText = '';
|
|
482
482
|
let requestCounter = 0;
|
|
483
483
|
let latestRequestId = -1;
|
|
484
|
+
let inferenceInFlight = false;
|
|
485
|
+
let activeInferenceText = null;
|
|
486
|
+
// The requestId of the inference currently in flight. Tracked so the
|
|
487
|
+
// in-flight dedup path can restore `latestRequestId` to it — otherwise an
|
|
488
|
+
// intermediate keystroke that bumped `latestRequestId` would cause the
|
|
489
|
+
// in-flight (still-current) result to be discarded as stale.
|
|
490
|
+
let activeInferenceRequestId = -1;
|
|
491
|
+
let pendingInference = null;
|
|
484
492
|
let ready = false;
|
|
485
493
|
let destroyed = false;
|
|
486
494
|
let initFailed = false;
|
|
@@ -708,8 +716,8 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
708
716
|
hasLmLogits: storedLmLogits !== null
|
|
709
717
|
});
|
|
710
718
|
} catch (err) {
|
|
711
|
-
// Discard errors for stale requests
|
|
712
|
-
if (requestId < latestRequestId) {
|
|
719
|
+
// Discard errors for stale requests or after teardown
|
|
720
|
+
if (requestId < latestRequestId || destroyed) {
|
|
713
721
|
return;
|
|
714
722
|
}
|
|
715
723
|
storedContextVector = null;
|
|
@@ -729,10 +737,46 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
729
737
|
|
|
730
738
|
// ── Context update (debounced) ─────────────────────────────────────────
|
|
731
739
|
|
|
740
|
+
const startInference = (text, requestId) => {
|
|
741
|
+
// Self-contained guard: never start a new inference cycle after teardown,
|
|
742
|
+
// regardless of caller discipline.
|
|
743
|
+
if (destroyed) {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
inferenceInFlight = true;
|
|
747
|
+
activeInferenceText = text;
|
|
748
|
+
activeInferenceRequestId = requestId;
|
|
749
|
+
void ensureEngineInitialized().then(() => runInference(text, requestId)).catch(() => {}).finally(() => {
|
|
750
|
+
inferenceInFlight = false;
|
|
751
|
+
activeInferenceText = null;
|
|
752
|
+
activeInferenceRequestId = -1;
|
|
753
|
+
const next = pendingInference;
|
|
754
|
+
pendingInference = null;
|
|
755
|
+
if (next && !destroyed) {
|
|
756
|
+
startInference(next.text, next.requestId);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
};
|
|
732
760
|
const doUpdateContext = text => {
|
|
761
|
+
var _pendingInference;
|
|
733
762
|
if (destroyed || !text || text.trim().length === 0) {
|
|
734
763
|
return;
|
|
735
764
|
}
|
|
765
|
+
if (inferenceInFlight && text === activeInferenceText) {
|
|
766
|
+
// The latest desired text already matches the in-flight inference, so
|
|
767
|
+
// re-running it would be wasted work. But an intermediate keystroke may
|
|
768
|
+
// have bumped `latestRequestId` past the in-flight request (and then been
|
|
769
|
+
// coalesced away), which would cause runInference to discard the
|
|
770
|
+
// still-current result as stale. Pin `latestRequestId` back to the active
|
|
771
|
+
// request so its result is accepted, and drop any now-superseded pending
|
|
772
|
+
// request.
|
|
773
|
+
latestRequestId = activeInferenceRequestId;
|
|
774
|
+
pendingInference = null;
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (inferenceInFlight && ((_pendingInference = pendingInference) === null || _pendingInference === void 0 ? void 0 : _pendingInference.text) === text) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
736
780
|
const requestId = ++requestCounter;
|
|
737
781
|
latestRequestId = requestId;
|
|
738
782
|
if (isAutocompleteDebugEnabled()) {
|
|
@@ -746,12 +790,27 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
746
790
|
// eslint-disable-next-line no-console
|
|
747
791
|
console.groupEnd();
|
|
748
792
|
}
|
|
749
|
-
|
|
793
|
+
if (inferenceInFlight) {
|
|
794
|
+
pendingInference = {
|
|
795
|
+
text,
|
|
796
|
+
requestId
|
|
797
|
+
};
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
startInference(text, requestId);
|
|
750
801
|
};
|
|
751
802
|
const updateContextDebounced = text => {
|
|
752
803
|
if (debounceTimer) {
|
|
753
804
|
clearTimeout(debounceTimer);
|
|
754
805
|
}
|
|
806
|
+
if (inferenceInFlight) {
|
|
807
|
+
pendingInference = null;
|
|
808
|
+
if (text === activeInferenceText) {
|
|
809
|
+
latestRequestId = activeInferenceRequestId;
|
|
810
|
+
lastRequestedText = text;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
755
814
|
lastRequestedText = text;
|
|
756
815
|
debounceTimer = setTimeout(() => {
|
|
757
816
|
debounceTimer = null;
|
|
@@ -784,6 +843,10 @@ export const createLocalSlowLaneClient = (config = {}) => {
|
|
|
784
843
|
unloadEngine(engineToUnload);
|
|
785
844
|
}
|
|
786
845
|
engineInitPromise = null;
|
|
846
|
+
inferenceInFlight = false;
|
|
847
|
+
activeInferenceText = null;
|
|
848
|
+
activeInferenceRequestId = -1;
|
|
849
|
+
pendingInference = null;
|
|
787
850
|
storedContextVector = null;
|
|
788
851
|
storedLmLogits = null;
|
|
789
852
|
}
|