move to SRS Algo

This commit is contained in:
aknuth 2024-12-11 21:33:52 +01:00
parent 26518bef56
commit ac69a11db5
3 changed files with 127 additions and 33 deletions

View File

@ -4,7 +4,6 @@ import { HttpClient } from '@angular/common/http';
import { map, Observable, switchMap } from 'rxjs'; import { map, Observable, switchMap } from 'rxjs';
export interface Deck { export interface Deck {
id: number; // Hinzugefügt
name: string; name: string;
images: DeckImage[]; images: DeckImage[];
} }
@ -16,10 +15,16 @@ export interface DeckImage {
} }
export interface Box { export interface Box {
id?:number;
x1:number; x1:number;
x2:number; x2:number;
y1:number; y1:number;
y2:number; y2:number;
due?: number;
ivl?: number;
factor?: number;
reps?: number;
lapses?: number;
} }
export interface BackendBox { export interface BackendBox {
@ -58,7 +63,6 @@ export class DeckService {
getDecks(): Observable<Deck[]> { getDecks(): Observable<Deck[]> {
return this.http.get<any[]>(this.apiUrl).pipe( return this.http.get<any[]>(this.apiUrl).pipe(
map(decks => decks.map(deck => ({ map(decks => decks.map(deck => ({
id: deck.id, // Annahme: Jeder Deck hat eine eindeutige ID
name: deck.name, name: deck.name,
images: this.groupImagesByName(deck.images) images: this.groupImagesByName(deck.images)
}))) })))
@ -76,10 +80,16 @@ export class DeckService {
}; };
} }
imageMap[image.id].boxes.push({ imageMap[image.id].boxes.push({
id: image.boxid,
x1: image.x1, x1: image.x1,
x2: image.x2, x2: image.x2,
y1: image.y1, 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> { moveImage(imageId: string, targetDeckId: number): Observable<any> {
return this.http.post(`${this.apiUrl}/images/${imageId}/move`, { targetDeckId }); 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);
}
} }

View File

@ -14,22 +14,31 @@
Anzeigen Anzeigen
</button> </button>
<!-- Gewusst Button --> <!-- Nochmal Button -->
<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" class="bg-blue-500 disabled:bg-blue-200 text-white py-2 px-4 rounded hover:bg-blue-600"
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length" [disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
> >
Gewusst Gut
</button> </button>
<!-- Nicht gewusst Button --> <!-- Einfach Button -->
<button <button
(click)="markUnknown()" (click)="markEasy()"
class="bg-red-500 disabled:bg-red-200 text-white py-2 px-4 rounded hover:bg-red-600" class="bg-green-500 disabled:bg-green-200 text-white py-2 px-4 rounded hover:bg-green-600"
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length" [disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
> >
Nicht gewusst Einfach
</button> </button>
<!-- Nächstes Bild Button --> <!-- Nächstes Bild Button -->
@ -43,7 +52,7 @@
</div> </div>
<p class="mt-2">{{ progress }}</p> <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 <button
(click)="closeTraining()" (click)="closeTraining()"

View File

@ -1,4 +1,4 @@
// src/app/training.component.ts // training.component.ts
import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
import { Deck, DeckImage, DeckService, Box } from '../deck.service'; import { Deck, DeckImage, DeckService, Box } from '../deck.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -19,20 +19,18 @@ export class TrainingComponent implements OnInit {
currentImageIndex: number = 0; currentImageIndex: number = 0;
currentImageData: DeckImage | null = null; currentImageData: DeckImage | null = null;
// Ändere currentBoxIndex zu boxesToReview als Array
currentBoxIndex: number = 0; currentBoxIndex: number = 0;
boxesToReview: Box[] = []; boxesToReview: Box[] = [];
boxRevealed: boolean[] = []; boxRevealed: boolean[] = [];
knownCount: number = 0;
unknownCount: number = 0;
isShowingBox: boolean = false; isShowingBox: boolean = false;
isTrainingFinished: boolean = false; isTrainingFinished: boolean = false;
constructor(private deckService: DeckService) { } constructor(private deckService: DeckService) { }
ngOnInit(): void { ngOnInit(): void {
// Initialisiere die boxesToReview basierend auf SRS
this.initializeBoxesToReview();
} }
ngAfterViewInit(){ 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 { loadImage(imageIndex: number): void {
if (imageIndex >= this.deck.images.length) { if (imageIndex >= this.deck.images.length) {
this.endTraining(); this.endTraining();
@ -51,8 +70,8 @@ export class TrainingComponent implements OnInit {
} }
this.currentImageData = this.deck.images[imageIndex]; this.currentImageData = this.deck.images[imageIndex];
// Initialisiere boxesToReview mit allen Boxen, gemischt // Initialisiere boxesToReview mit allen Boxen, die fällig sind, gemischt
this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes]); this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes].filter(box => box.due! <= this.getTodayInDays()));
this.boxRevealed = new Array(this.boxesToReview.length).fill(false); this.boxRevealed = new Array(this.boxesToReview.length).fill(false);
this.isShowingBox = false; this.isShowingBox = false;
this.drawCanvas(); this.drawCanvas();
@ -132,23 +151,73 @@ export class TrainingComponent implements OnInit {
this.drawCanvas(); this.drawCanvas();
} }
markKnown(): void { // Neue Methoden für Anki-Optionen
this.knownCount++;
// Entferne die aktuelle Box aus boxesToReview, da sie bekannt ist async markAgain(): Promise<void> {
this.boxesToReview.splice(this.currentBoxIndex, 1); await this.updateCard('again');
this.boxRevealed.splice(this.currentBoxIndex, 1);
this.nextBox(); this.nextBox();
} }
markUnknown(): void { async markGood(): Promise<void> {
this.unknownCount++; await this.updateCard('good');
// 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);
this.nextBox(); 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 { nextBox(): void {
this.isShowingBox = false; this.isShowingBox = false;
@ -158,10 +227,12 @@ export class TrainingComponent implements OnInit {
return; return;
} }
if (this.currentBoxIndex >= this.boxesToReview.length) { if (this.currentBoxIndex >= this.boxesToReview.length - 1) {
this.currentBoxIndex = 0; this.currentBoxIndex = 0;
} else {
this.currentBoxIndex++;
} }
this.boxRevealed[this.currentBoxIndex] = false;
this.drawCanvas(); this.drawCanvas();
} }
@ -181,7 +252,8 @@ export class TrainingComponent implements OnInit {
endTraining(): void { endTraining(): void {
this.isTrainingFinished = true; 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(); this.close.emit();
} }
@ -195,4 +267,3 @@ export class TrainingComponent implements OnInit {
return `Bild ${this.currentImageIndex + 1} von ${this.deck.images.length}`; return `Bild ${this.currentImageIndex + 1} von ${this.deck.images.length}`;
} }
} }