Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SF-3028 Add final confirmation step when generating drafts #2787

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ <h4 class="explanation">
<div class="button-strip">
<button mat-stroked-button matStepperPrevious>{{ t("back") }}</button>
<button mat-flat-button (click)="tryAdvanceStep()" color="primary">
{{ featureFlags.allowFastTraining.enabled || trainingDataFilesAvailable ? t("next") : t("generate_draft") }}
{{ t("next") }}
</button>
</div>
</mat-step>
Expand All @@ -135,7 +135,7 @@ <h1 class="mat-headline-4">{{ t("choose_additional_training_data_files") }}</h1>
<div class="button-strip">
<button mat-stroked-button matStepperPrevious>{{ t("back") }}</button>
<button mat-flat-button (click)="tryAdvanceStep()" color="primary">
{{ featureFlags.allowFastTraining.enabled ? t("next") : t("generate_draft") }}
{{ t("next") }}
</button>
</div>
</mat-step>
Expand All @@ -155,11 +155,67 @@ <h1 class="mat-headline-4">{{ t("configure_advanced_settings") }}</h1>
<div class="button-strip">
<button mat-stroked-button matStepperPrevious>{{ t("back") }}</button>
<button mat-flat-button (click)="tryAdvanceStep()" color="primary">
{{ t("generate_draft") }}
{{ t("next") }}
</button>
</div>
</mat-step>
}
<mat-step [completed]="true">
<ng-template matStepLabel>
{{ t("confirm_label") }}
</ng-template>
<h1 class="mat-headline-4">{{ t("confirm") }}</h1>

<h2>Training the language model</h2>
<mat-card class="confirm-section mat-elevation-z2">
<span class="explanation">{{ t("confirm_training") }}</span>
<table mat-table class="confirm-training" [dataSource]="selectedTrainingBooksCollapsed()">
<ng-container matColumnDef="book">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element" class="bookName">
<div class="confirm-books cell-padding-block">
@for (range of element.ranges; track range) {
<span class="confirm-book">{{ range }}</span>
}
</div>
</td>
</ng-container>
<ng-container matColumnDef="source">
<th mat-header-cell *matHeaderCellDef>
{{ this.i18n.getLanguageDisplayName(trainingSources[0].writingSystem.tag) }}
</th>
<td mat-cell *matCellDef="let element">{{ element.source }}</td>
</ng-container>
<ng-container matColumnDef="target">
<th mat-header-cell *matHeaderCellDef>
{{ this.i18n.getLanguageDisplayName(trainingTargets[0].writingSystem.tag) }}
</th>
<td mat-cell *matCellDef="let element">{{ element.target }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['book', 'source', 'target']"></tr>
<tr mat-row *matRowDef="let row; columns: ['book', 'source', 'target']"></tr>
</table>
</mat-card>

<h2>
Drafting from <strong>{{ trainingSources[0].shortName }}</strong>
</h2>
<mat-card class="confirm-section mat-elevation-z2">
<span class="explanation">{{ t("confirm_translate") }}</span>
<div class="confirm-translate">
<div class="confirm-books confirm-translate-books">
<span class="confirm-book">{{ selectedTranslateBooks() }}</span>
</div>
</div>
</mat-card>

<div class="button-strip">
<button mat-stroked-button matStepperPrevious>{{ t("back") }}</button>
<button mat-flat-button (click)="tryAdvanceStep()" color="primary">
{{ t("generate_draft") }}
</button>
</div>
</mat-step>
</mat-stepper>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
@use 'src/variables';

h1 {
margin: 12px 0;
margin: 24px 0;
}

h2 {
font-weight: 400;
margin-bottom: 0.5em;
}

// Prevent font increase when selecting a step
Expand Down Expand Up @@ -34,7 +39,7 @@ h1 {
}

.button-strip {
margin-top: 20px;
margin-top: 60px;
display: flex;
justify-content: flex-start;
gap: 8px;
Expand Down Expand Up @@ -90,3 +95,53 @@ app-notice {
margin-right: 1em;
}
}

.confirm-section {
display: flex;
row-gap: 8px;
flex-direction: column;
padding: 1.5em;
margin-block: 0 2em;
border-radius: 4px;
max-width: 750px;
}

.explanation {
font-style: italic;
display: inline-block;
margin-bottom: 12px;
font-weight: 100;
}

.mat-mdc-table {
background-color: initial;
}

.bookName {
font-size: 1.1em;
}

.confirm-translate {
display: flex;
flex-direction: row;
}

.confirm-translate-books {
padding-inline-start: 16px;
}

.cell-padding-block {
padding-block: 16px;
}

.confirm-books {
display: flex;
flex-direction: column;
column-gap: 32px;
row-gap: 8px;
}

.confirm-book {
padding-block: 4px;
font-size: 16px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { MatStepper } from '@angular/material/stepper';
import { TranslocoModule } from '@ngneat/transloco';
import { Canon } from '@sillsdev/scripture';
import { TranslocoMarkupModule } from 'ngx-transloco-markup';
import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
import { TrainingData } from 'realtime-server/lib/esm/scriptureforge/models/training-data';
import { TranslateSource } from 'realtime-server/lib/esm/scriptureforge/models/translate-config';
import { Subscription, merge } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
Expand Down Expand Up @@ -31,6 +33,22 @@ export interface DraftGenerationStepsResult {
fastTraining: boolean;
}

export interface Book {
name: string;
number: number;
}

export interface TrainingBook extends Book, TrainingPair {}

export interface TrainingGroup extends TrainingPair {
ranges: string[];
}

interface TrainingPair {
source: string;
target: string;
}

@Component({
selector: 'app-draft-generation-steps',
templateUrl: './draft-generation-steps.component.html',
Expand Down Expand Up @@ -63,8 +81,8 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

initialSelectedTrainingBooks: number[] = [];
initialSelectedTranslateBooks: number[] = [];
userSelectedTrainingBooks: number[] = [];
userSelectedTranslateBooks: number[] = [];
userSelectedTrainingBooks: TrainingBook[] = [];
userSelectedTranslateBooks: Book[] = [];

selectedTrainingDataIds: string[] = [];

Expand All @@ -87,15 +105,35 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
private trainingDataQuery?: RealtimeQuery<TrainingDataDoc>;
private trainingDataSub?: Subscription;

readonly trainingSources: TranslateSource[] = [];
readonly trainingTargets: SFProjectProfile[] = [];

constructor(
private readonly activatedProject: ActivatedProjectService,
private readonly draftSourcesService: DraftSourcesService,
readonly featureFlags: FeatureFlagService,
protected readonly featureFlags: FeatureFlagService,
private readonly nllbLanguageService: NllbLanguageService,
private readonly trainingDataService: TrainingDataService,
readonly i18n: I18nService
protected readonly i18n: I18nService
) {
super();
const project = activatedProject.projectDoc!.data!;
this.trainingTargets.push(project);

let trainingSource: TranslateSource | undefined;
if (project.translateConfig.draftConfig.alternateTrainingSourceEnabled) {
trainingSource = project.translateConfig.draftConfig.alternateTrainingSource;
} else {
trainingSource = project.translateConfig.source;
}

if (trainingSource != null) {
this.trainingSources.push(trainingSource);
}

if (project.translateConfig.draftConfig.additionalTrainingSourceEnabled) {
this.trainingSources.push(project.translateConfig.draftConfig.additionalTrainingSource!);
}
}

ngOnInit(): void {
Expand Down Expand Up @@ -218,8 +256,60 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
);
}

selectedTrainingBooksCollapsed(): TrainingGroup[] {
const continguousGroups: TrainingGroup[] = [];
let currentGroup: TrainingBook[] = [];
for (const book of this.userSelectedTrainingBooks) {
const isBookConsecutiveAndMatching =
book.source === currentGroup[0]?.source &&
book.target === currentGroup[0]?.target &&
book.number === currentGroup[currentGroup.length - 1]?.number + 1;
if (currentGroup.length > 0 && !isBookConsecutiveAndMatching) {
//process and reset current group
addGroup(currentGroup, this.i18n);
currentGroup.length = 0;
}
//add book to current group
currentGroup.push(book);
}

//add last group
if (currentGroup.length > 0) {
addGroup(currentGroup, this.i18n);
}

const groupsCollapsed: TrainingGroup[] = [];
for (const group of continguousGroups) {
const matchIndex = groupsCollapsed.findIndex(g => g.source === group.source && g.target === group.target);
if (matchIndex === -1) {
//make a new group for this source/target
groupsCollapsed.push(group);
} else {
//append the current group onto the matching group
groupsCollapsed[matchIndex].ranges.push(group.ranges[0]);
}
}

return groupsCollapsed;

function addGroup(group: TrainingBook[], i18n: I18nService): void {
let range;
if (group.length === 1) {
range = i18n.localizeBook(group[0].number);
} else {
range = i18n.localizeBook(group[0].number) + ' - ' + i18n.localizeBook(group[group.length - 1].number);
}
continguousGroups.push({ ranges: [range], source: group[0].source, target: group[0].target });
}
}

onTrainingBookSelect(selectedBooks: number[]): void {
this.userSelectedTrainingBooks = selectedBooks;
this.userSelectedTrainingBooks = selectedBooks.map((bookNum: number) => ({
number: bookNum,
name: Canon.bookNumberToEnglishName(bookNum),
source: this.trainingSources[0].shortName,
target: this.trainingTargets[0].shortName
}));
this.clearErrorMessage();
}

Expand All @@ -229,7 +319,10 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
}

onTranslateBookSelect(selectedBooks: number[]): void {
this.userSelectedTranslateBooks = selectedBooks;
this.userSelectedTranslateBooks = selectedBooks.map((bookNum: number) => ({
number: bookNum,
name: Canon.bookNumberToEnglishName(bookNum)
}));
this.clearErrorMessage();
}

Expand All @@ -247,31 +340,35 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem
this.stepper.next();
} else {
this.done.emit({
trainingBooks: this.userSelectedTrainingBooks,
trainingBooks: this.userSelectedTrainingBooks.map(book => book.number),
trainingDataFiles: this.selectedTrainingDataIds,
translationBooks: this.userSelectedTranslateBooks,
translationBooks: this.userSelectedTranslateBooks.map(book => book.number),
fastTraining: this.fastTraining
});
}
}

selectedTranslateBooks(): string {
return this.userSelectedTranslateBooks.map(b => b.name).join(', ');
}

/**
* Filter selected translate books from available/selected training books.
* Currently, training books cannot in the set of translate books,
* but this requirement may be removed in the future.
*/
updateTrainingBooks(): void {
const selectedTranslateBooks = new Set<number>(this.userSelectedTranslateBooks);
const selectedTranslateBooks = new Set<number>(this.userSelectedTranslateBooks.map(book => book.number));

this.availableTrainingBooks = this.initialAvailableTrainingBooks.filter(
bookNum => !selectedTranslateBooks.has(bookNum)
);

const newSelectedTrainingBooks = this.userSelectedTrainingBooks.filter(
bookNum => !selectedTranslateBooks.has(bookNum)
book => !selectedTranslateBooks.has(book.number)
);

this.initialSelectedTrainingBooks = newSelectedTrainingBooks;
this.initialSelectedTrainingBooks = newSelectedTrainingBooks.map(book => book.number);
this.userSelectedTrainingBooks = newSelectedTrainingBooks;
}

Expand Down Expand Up @@ -300,7 +397,10 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

// Set the selected books to the intersection, or if the intersection is empty, do not select any
this.initialSelectedTranslateBooks = intersection.length > 0 ? intersection : [];
this.userSelectedTranslateBooks = this.initialSelectedTranslateBooks;
this.userSelectedTranslateBooks = this.initialSelectedTranslateBooks.map((bookNum: number) => ({
number: bookNum,
name: Canon.bookNumberToEnglishName(bookNum)
}));
}

private setInitialTrainingBooks(availableBooks: number[]): void {
Expand All @@ -314,7 +414,13 @@ export class DraftGenerationStepsComponent extends SubscriptionDisposable implem

// Set the selected books to the intersection, or if the intersection is empty, do not select any
this.initialSelectedTrainingBooks = intersection.length > 0 ? intersection : [];
this.userSelectedTrainingBooks = this.initialSelectedTrainingBooks;

this.userSelectedTrainingBooks = this.initialSelectedTrainingBooks.map((bookNum: number) => ({
number: bookNum,
name: Canon.bookNumberToEnglishName(bookNum),
source: this.trainingSources[0].shortName,
target: this.trainingTargets[0].shortName
}));
}

private setInitialTrainingDataFiles(availableDataFiles: string[]): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@
"choose_books_to_translate_label": "Drafting",
"configure_advanced_settings": "Advanced settings",
"configure_advanced_settings_label": "Advanced",
"confirm": "Confirm options",
"confirm_label": "Confirm",
"confirm_training": "A language model will be created using the following example translations:",
"confirm_translate": "The language model will then translate these books:",
"fast_training_warning": "Enabling this will save time but greatly reduce accuracy.",
"fast_training": "Enable Fast Training",
"generate_draft": "Generate draft",
Expand Down
Loading