diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts index 50ccbed73f..f955f3e675 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts @@ -146,13 +146,15 @@ export class SearchNode { private readonly deleteAfterInsertEditPairs: number; /** - * A unique identifier corresponding to the earliest SearchPath containing - * the correction-search graph edge represented by this instance. + * A unique identifier corresponding to the SearchQuotientNode last passed + * through by the represented search path. * - * Corresponding search results will be tagged with this, which can be used - * to identify the result's original source tokenization. + * The correction-search results produced by this search path will be tagged + * accordingly to match the correction with its original ContextTokenization. + * This is necessary in order to properly construct suggestions that apply as + * the user expects should the tokenization pattern itself be corrected. */ - readonly spaceId: number; + public spaceId: number; /** * Notes the edit operation used for the most recent edge in the node's @@ -580,7 +582,7 @@ export class SearchNode { * @param timer * @returns */ -export async function *getBestMatches(searchModules: SearchQuotientNode[], timer: ExecutionTimer): AsyncGenerator { +export async function *getBestMatches(searchModules: SearchQuotientNode[], timer: ExecutionTimer): AsyncGenerator> { const spaceQueue = new PriorityQueue((a, b) => a.currentCost - b.currentCost); // Stage 1 - if we already have extracted results, build a queue just for them diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts index e8741c2cdd..07398214fe 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts @@ -47,8 +47,7 @@ export class LegacyQuotientRoot extends SearchQuotientRoot { return { type: 'complete', cost: node.currentCost, - mapping: new TokenResultMapping(node), - spaceId: this.spaceId + mapping: new TokenResultMapping(node) }; } diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts index 02c89e295a..504d36c2a9 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts @@ -87,6 +87,7 @@ export class SearchQuotientCluster implements SearchQuotientNode { this.lowestPossibleSingleCost = lowestPossibleSingleCost; this.completedPaths = inboundPaths.flatMap(p => p.previousResults).map(r => r.node); + this.completedPaths.forEach((p) => p.spaceId = this.spaceId); this.selectionQueue.enqueueAll(inboundPaths); return; @@ -144,15 +145,16 @@ export class SearchQuotientCluster implements SearchQuotientNode { this.selectionQueue.enqueue(bestPath); if(currentResult.type == 'complete') { - this.completedPaths?.push(currentResult.mapping.node); - currentResult.spaceId = this.spaceId; + const node = currentResult.mapping.node; + node.spaceId = this.spaceId; + this.completedPaths?.push(node); } return currentResult; } public get previousResults(): TokenResultMapping[] { - return this.completedPaths?.map((n => new TokenResultMapping(n, this.spaceId))) ?? []; + return this.completedPaths?.map((n) => new TokenResultMapping(n)) ?? []; } get model(): LexicalModelTypes.LexicalModel { diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts index eeb99a63b4..bd5bb88989 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts @@ -31,8 +31,7 @@ type IntermediateSearchPath = { type CompleteSearchPath = { type: 'complete', cost: number, - mapping: TokenResultMapping, - spaceId: number + mapping: TokenResultMapping } export type PathResult = NullPath | IntermediateSearchPath | CompleteSearchPath; diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-root.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-root.ts index dac23fc6b6..e6ef46b81b 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-root.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-root.ts @@ -77,8 +77,7 @@ export class SearchQuotientRoot implements SearchQuotientNode { return { type: 'complete', cost: 0, - mapping: new TokenResultMapping(this.rootNode), - spaceId: this.spaceId + mapping: new TokenResultMapping(this.rootNode) }; } diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts index f2a8644117..43cbd2a4e4 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts @@ -404,8 +404,7 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode { return { type: 'complete', cost: currentNode.currentCost, - mapping: new TokenResultMapping(currentNode), - spaceId: this.spaceId + mapping: new TokenResultMapping(currentNode) }; } diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts index d177b54bb0..7e048148b0 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts @@ -1,3 +1,13 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by jahorton on 2026-04-02 + * + * This file defines the type used for tracking critical graph-search properties + * utilized during correction-search by the `getBestMatches` algorithm when run + * against individual tokens / words. + */ + import { LexicalModelTypes } from '@keymanapp/common-types'; import { SearchNode, TraversableToken } from "./distance-modeler.js"; @@ -9,12 +19,8 @@ import Transform = LexicalModelTypes.Transform; export class TokenResultMapping { readonly node: SearchNode; - // Supports SearchPath -> SearchSpace remapping. - readonly spaceId: number; - - constructor(node: SearchNode, spaceId?: number) { + constructor(node: SearchNode) { this.node = node; - this.spaceId = spaceId ?? node.spaceId; } get inputSequence(): ProbabilityMass[] { diff --git a/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts b/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts index c8d1204ac6..bff12b04b6 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts @@ -536,7 +536,7 @@ export async function correctAndEnumerate( const correctionPredictionMap: Record> = {}; for await(const match of getBestMatches(searchModules, timer)) { // Corrections obtained: now to predict from them! - const tokenization = tokenizations.find(t => t.spaceId == match.spaceId); + const tokenization = tokenizations.find(t => t.spaceId == match.node.spaceId); // If our 'match' results in fully deleting the new token, reject it and try again. if(match.matchSequence.length == 0 && match.inputSequence.length != 0) {