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';
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()"
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue