move to SRS Algo
This commit is contained in:
parent
26518bef56
commit
ac69a11db5
|
|
@ -4,7 +4,6 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { map, Observable, switchMap } from 'rxjs';
|
||||
|
||||
export interface Deck {
|
||||
id: number; // Hinzugefügt
|
||||
name: string;
|
||||
images: DeckImage[];
|
||||
}
|
||||
|
|
@ -16,10 +15,16 @@ export interface DeckImage {
|
|||
}
|
||||
|
||||
export interface Box {
|
||||
id?:number;
|
||||
x1:number;
|
||||
x2:number;
|
||||
y1:number;
|
||||
y2:number;
|
||||
due?: number;
|
||||
ivl?: number;
|
||||
factor?: number;
|
||||
reps?: number;
|
||||
lapses?: number;
|
||||
}
|
||||
|
||||
export interface BackendBox {
|
||||
|
|
@ -58,7 +63,6 @@ export class DeckService {
|
|||
getDecks(): Observable<Deck[]> {
|
||||
return this.http.get<any[]>(this.apiUrl).pipe(
|
||||
map(decks => decks.map(deck => ({
|
||||
id: deck.id, // Annahme: Jeder Deck hat eine eindeutige ID
|
||||
name: deck.name,
|
||||
images: this.groupImagesByName(deck.images)
|
||||
})))
|
||||
|
|
@ -76,10 +80,16 @@ export class DeckService {
|
|||
};
|
||||
}
|
||||
imageMap[image.id].boxes.push({
|
||||
id: image.boxid,
|
||||
x1: image.x1,
|
||||
x2: image.x2,
|
||||
y1: image.y1,
|
||||
y2: image.y2
|
||||
y2: image.y2,
|
||||
due: image.due,
|
||||
ivl:image.ivl,
|
||||
factor:image.factor,
|
||||
reps:image.reps,
|
||||
lapses:image.lapses
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -110,4 +120,8 @@ export class DeckService {
|
|||
moveImage(imageId: string, targetDeckId: number): Observable<any> {
|
||||
return this.http.post(`${this.apiUrl}/images/${imageId}/move`, { targetDeckId });
|
||||
}
|
||||
|
||||
updateBox(box: Box): Observable<any> {
|
||||
return this.http.put(`${this.apiUrl}/boxes/${box.id}`, box);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,22 +14,31 @@
|
|||
Anzeigen
|
||||
</button>
|
||||
|
||||
<!-- Gewusst Button -->
|
||||
<!-- Nochmal Button -->
|
||||
<button
|
||||
(click)="markKnown()"
|
||||
(click)="markAgain()"
|
||||
class="bg-orange-500 disabled:bg-orange-200 text-white py-2 px-4 rounded hover:bg-orange-600"
|
||||
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
|
||||
>
|
||||
Nochmal
|
||||
</button>
|
||||
|
||||
<!-- Gut Button -->
|
||||
<button
|
||||
(click)="markGood()"
|
||||
class="bg-blue-500 disabled:bg-blue-200 text-white py-2 px-4 rounded hover:bg-blue-600"
|
||||
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
|
||||
>
|
||||
Gewusst
|
||||
Gut
|
||||
</button>
|
||||
|
||||
<!-- Nicht gewusst Button -->
|
||||
<!-- Einfach Button -->
|
||||
<button
|
||||
(click)="markUnknown()"
|
||||
class="bg-red-500 disabled:bg-red-200 text-white py-2 px-4 rounded hover:bg-red-600"
|
||||
(click)="markEasy()"
|
||||
class="bg-green-500 disabled:bg-green-200 text-white py-2 px-4 rounded hover:bg-green-600"
|
||||
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
|
||||
>
|
||||
Nicht gewusst
|
||||
Einfach
|
||||
</button>
|
||||
|
||||
<!-- Nächstes Bild Button -->
|
||||
|
|
@ -43,7 +52,7 @@
|
|||
</div>
|
||||
|
||||
<p class="mt-2">{{ progress }}</p>
|
||||
<p class="mt-2">Gewusst: {{ knownCount }} | Nicht gewusst: {{ unknownCount }}</p>
|
||||
<!-- <p class="mt-2">Gewusst: {{ knownCount }} | Nicht gewusst: {{ unknownCount }}</p> -->
|
||||
|
||||
<button
|
||||
(click)="closeTraining()"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// src/app/training.component.ts
|
||||
// training.component.ts
|
||||
import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Deck, DeckImage, DeckService, Box } from '../deck.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
|
@ -19,20 +19,18 @@ export class TrainingComponent implements OnInit {
|
|||
|
||||
currentImageIndex: number = 0;
|
||||
currentImageData: DeckImage | null = null;
|
||||
// Ändere currentBoxIndex zu boxesToReview als Array
|
||||
currentBoxIndex: number = 0;
|
||||
boxesToReview: Box[] = [];
|
||||
boxRevealed: boolean[] = [];
|
||||
|
||||
knownCount: number = 0;
|
||||
unknownCount: number = 0;
|
||||
|
||||
isShowingBox: boolean = false;
|
||||
isTrainingFinished: boolean = false;
|
||||
|
||||
constructor(private deckService: DeckService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialisiere die boxesToReview basierend auf SRS
|
||||
this.initializeBoxesToReview();
|
||||
}
|
||||
|
||||
ngAfterViewInit(){
|
||||
|
|
@ -44,6 +42,27 @@ export class TrainingComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
initializeBoxesToReview(): void {
|
||||
// Filtere alle Boxen, die fällig sind (due <= heute)
|
||||
const today = this.getTodayInDays();
|
||||
this.deck.images.forEach(image => {
|
||||
image.boxes.forEach(box => {
|
||||
if (box.due === undefined || box.due <= today) {
|
||||
this.boxesToReview.push(box);
|
||||
this.boxRevealed.push(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Mische die Boxen
|
||||
this.boxesToReview = this.shuffleArray(this.boxesToReview);
|
||||
}
|
||||
|
||||
getTodayInDays(): number {
|
||||
const epoch = new Date(1970, 0, 1); // Anki verwendet UNIX-Epoch
|
||||
const today = new Date();
|
||||
return Math.floor((today.getTime() - epoch.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
loadImage(imageIndex: number): void {
|
||||
if (imageIndex >= this.deck.images.length) {
|
||||
this.endTraining();
|
||||
|
|
@ -51,8 +70,8 @@ export class TrainingComponent implements OnInit {
|
|||
}
|
||||
|
||||
this.currentImageData = this.deck.images[imageIndex];
|
||||
// Initialisiere boxesToReview mit allen Boxen, gemischt
|
||||
this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes]);
|
||||
// Initialisiere boxesToReview mit allen Boxen, die fällig sind, gemischt
|
||||
this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes].filter(box => box.due! <= this.getTodayInDays()));
|
||||
this.boxRevealed = new Array(this.boxesToReview.length).fill(false);
|
||||
this.isShowingBox = false;
|
||||
this.drawCanvas();
|
||||
|
|
@ -132,23 +151,73 @@ export class TrainingComponent implements OnInit {
|
|||
this.drawCanvas();
|
||||
}
|
||||
|
||||
markKnown(): void {
|
||||
this.knownCount++;
|
||||
// Entferne die aktuelle Box aus boxesToReview, da sie bekannt ist
|
||||
this.boxesToReview.splice(this.currentBoxIndex, 1);
|
||||
this.boxRevealed.splice(this.currentBoxIndex, 1);
|
||||
// Neue Methoden für Anki-Optionen
|
||||
|
||||
async markAgain(): Promise<void> {
|
||||
await this.updateCard('again');
|
||||
this.nextBox();
|
||||
}
|
||||
|
||||
markUnknown(): void {
|
||||
this.unknownCount++;
|
||||
// Behalte die aktuelle Box in der Liste und verschiebe sie an eine zufällige Position am Ende
|
||||
const box = this.boxesToReview.splice(this.currentBoxIndex, 1)[0];
|
||||
this.boxesToReview.push(box);
|
||||
this.boxRevealed.splice(this.currentBoxIndex, 1);
|
||||
async markGood(): Promise<void> {
|
||||
await this.updateCard('good');
|
||||
this.nextBox();
|
||||
}
|
||||
|
||||
async markEasy(): Promise<void> {
|
||||
await this.updateCard('easy');
|
||||
this.nextBox();
|
||||
}
|
||||
|
||||
async updateCard(action: 'again' | 'good' | 'easy'): Promise<void> {
|
||||
if (this.currentBoxIndex >= this.boxesToReview.length) return;
|
||||
|
||||
const box = this.boxesToReview[this.currentBoxIndex];
|
||||
const today = this.getTodayInDays();
|
||||
|
||||
let newIvl = box.ivl || 0;
|
||||
let newFactor = box.factor || 2.5;
|
||||
let newReps = box.reps || 0;
|
||||
let newLapses = box.lapses || 0;
|
||||
|
||||
switch(action) {
|
||||
case 'again':
|
||||
newIvl = 1 / 1440; // weniger als ein Tag, z.B., 1 Minute in Tagen
|
||||
newReps = 0;
|
||||
newLapses += 1;
|
||||
break;
|
||||
case 'good':
|
||||
if (newReps === 0) {
|
||||
newIvl = 1; // nächste Wiederholung am nächsten Tag
|
||||
} else {
|
||||
newIvl = newIvl * newFactor;
|
||||
}
|
||||
newReps += 1;
|
||||
break;
|
||||
case 'easy':
|
||||
if (newReps === 0) {
|
||||
newIvl = 4; // nächste Wiederholung in 4 Tagen
|
||||
} else {
|
||||
newIvl = newIvl * newFactor * 1.3; // Anki's "easy" kann zu einem leicht erhöhten Intervall führen
|
||||
}
|
||||
newReps += 1;
|
||||
newFactor = newFactor * 1.15; // optional: Anki erhöht den Faktor leicht
|
||||
break;
|
||||
}
|
||||
|
||||
// Update due
|
||||
const nextDue = today + Math.floor(newIvl);
|
||||
|
||||
// Aktualisiere das Box-Objekt
|
||||
box.ivl = newIvl;
|
||||
box.factor = newFactor;
|
||||
box.reps = newReps;
|
||||
box.lapses = newLapses;
|
||||
box.due = nextDue;
|
||||
|
||||
// Sende das Update an das Backend
|
||||
await this.deckService.updateBox(box).toPromise();
|
||||
}
|
||||
|
||||
nextBox(): void {
|
||||
this.isShowingBox = false;
|
||||
|
||||
|
|
@ -158,10 +227,12 @@ export class TrainingComponent implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.currentBoxIndex >= this.boxesToReview.length) {
|
||||
if (this.currentBoxIndex >= this.boxesToReview.length - 1) {
|
||||
this.currentBoxIndex = 0;
|
||||
} else {
|
||||
this.currentBoxIndex++;
|
||||
}
|
||||
|
||||
this.boxRevealed[this.currentBoxIndex] = false;
|
||||
this.drawCanvas();
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +252,8 @@ export class TrainingComponent implements OnInit {
|
|||
|
||||
endTraining(): void {
|
||||
this.isTrainingFinished = true;
|
||||
alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);
|
||||
//alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);
|
||||
alert(`Training beendet!`);
|
||||
this.close.emit();
|
||||
}
|
||||
|
||||
|
|
@ -195,4 +267,3 @@ export class TrainingComponent implements OnInit {
|
|||
return `Bild ${this.currentImageIndex + 1} von ${this.deck.images.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue