Skip to content

Commit

Permalink
Merge branch 'ankitects:main' into lazy_static-to-once_cell-to-stabil…
Browse files Browse the repository at this point in the history
…ized
  • Loading branch information
twwn authored Sep 27, 2024
2 parents 32515f1 + 1b7390f commit c3c89e7
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .buildkite/linux/release-entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export BUILD_ROOT=/state/build
export RELEASE=2
ln -sf out/node_modules .

echo "--- Install n2"
./tools/install-n2

if [ $(uname -m) = "aarch64" ]; then
./ninja wheels:anki
else
Expand Down
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.06.3
24.10
6 changes: 6 additions & 0 deletions ftl/core/card-stats.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ card-stats-review-log-type-review = Review
card-stats-review-log-type-relearn = Relearn
card-stats-review-log-type-filtered = Filtered
card-stats-review-log-type-manual = Manual
card-stats-review-log-elapsed-time = Elapsed Time
card-stats-no-card = (No card to display.)
card-stats-custom-data = Custom Data
card-stats-fsrs-stability = Stability
card-stats-fsrs-difficulty = Difficulty
card-stats-fsrs-retrievability = Retrievability
card-stats-fsrs-forgetting-curve-title = Forgetting Curve
card-stats-fsrs-forgetting-curve-first-week = First Week
card-stats-fsrs-forgetting-curve-first-month = First Month
card-stats-fsrs-forgetting-curve-first-year = First Year
card-stats-fsrs-forgetting-curve-all-time = All Time
## Window Titles

Expand Down
2 changes: 1 addition & 1 deletion ftl/qt-repo
3 changes: 2 additions & 1 deletion ftl/qt/qt-misc.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ qt-misc-please-select-1-card = (please select 1 card)
qt-misc-please-select-a-deck = Please select a deck.
qt-misc-please-use-fileimport-to-import-this = Please use File>Import to import this file.
qt-misc-processing = Processing...
qt-misc-replace-your-collection-with-an-earlier = Replace your collection with an earlier backup from { $val }?
qt-misc-replace-your-collection-with-an-earlier2 = Replace your collection with an earlier backup from { $val }?
qt-misc-revert-to-backup = Revert to backup
# please do not change the quote character, and please only change the font name if you have confirmed the new name is a valid Windows font
qt-misc-segoe-ui = "Segoe UI"
Expand Down Expand Up @@ -77,3 +77,4 @@ qt-misc-layout-horizontal-enabled = Horizontal layout enabled
## deprecated- these strings will be removed in the future, and do not need
## to be translated

qt-misc-replace-your-collection-with-an-earlier = Replace your collection with an earlier backup?
1 change: 1 addition & 0 deletions proto/anki/stats.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ message CardStatsResponse {
// per mill
uint32 ease = 5;
float taken_secs = 6;
optional cards.FsrsMemoryState memory_state = 7;
}
repeated StatsRevlogEntry revlog = 1;
int64 card_id = 2;
Expand Down
2 changes: 1 addition & 1 deletion qt/aqt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def onOpenBackup(self) -> None:

def do_open(path: str) -> None:
if not askUser(
tr.qt_misc_replace_your_collection_with_an_earlier(
tr.qt_misc_replace_your_collection_with_an_earlier2(
os.path.basename(path)
),
msgfunc=QMessageBox.warning,
Expand Down
45 changes: 44 additions & 1 deletion rslib/src/stats/card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::card::CardQueue;
use crate::card::CardType;
use crate::prelude::*;
use crate::revlog::RevlogEntry;
use crate::scheduler::fsrs::memory_state::single_card_revlog_to_item;
use crate::scheduler::fsrs::weights::ignore_revlogs_before_ms_from_config;
use crate::scheduler::timing::is_unix_epoch_timestamp;

impl Collection {
Expand Down Expand Up @@ -70,7 +72,7 @@ impl Collection {
total_secs,
card_type: nt.get_template(card.template_idx)?.name.clone(),
notetype: nt.name.clone(),
revlog: revlog.iter().rev().map(stats_revlog_entry).collect(),
revlog: self.stats_revlog_entries_with_memory_state(&card, revlog)?,
memory_state: card.memory_state.map(Into::into),
fsrs_retrievability,
custom_data: card.custom_data,
Expand Down Expand Up @@ -113,6 +115,46 @@ impl Collection {
),
})
}

fn stats_revlog_entries_with_memory_state(
self: &mut Collection,
card: &Card,
revlog: Vec<RevlogEntry>,
) -> Result<Vec<anki_proto::stats::card_stats_response::StatsRevlogEntry>> {
let deck_id = card.original_deck_id.or(card.deck_id);
let deck = self.get_deck(deck_id)?.or_not_found(card.deck_id)?;
let conf_id = DeckConfigId(deck.normal()?.config_id);
let config = self
.storage
.get_deck_config(conf_id)?
.or_not_found(conf_id)?;
let historical_retention = config.inner.historical_retention;
let fsrs = FSRS::new(Some(&config.inner.fsrs_weights))?;
let next_day_at = self.timing_today()?.next_day_at;
let ignore_before = ignore_revlogs_before_ms_from_config(&config)?;

let mut result = Vec::new();
let mut accumulated_revlog = Vec::new();

for entry in revlog {
accumulated_revlog.push(entry.clone());
let item = single_card_revlog_to_item(
&fsrs,
accumulated_revlog.clone(),
next_day_at,
historical_retention,
ignore_before,
)?;
let mut card_clone = card.clone();
card_clone.set_memory_state(&fsrs, item, historical_retention)?;

let mut stats_entry = stats_revlog_entry(&entry);
stats_entry.memory_state = card_clone.memory_state.map(Into::into);
result.push(stats_entry);
}

Ok(result.into_iter().rev().collect())
}
}

fn average_and_total_secs_strings(revlog: &[RevlogEntry]) -> (f32, f32) {
Expand All @@ -138,6 +180,7 @@ fn stats_revlog_entry(
interval: entry.interval_secs(),
ease: entry.ease_factor,
taken_secs: entry.taken_millis as f32 / 1000.,
memory_state: None,
}
}

Expand Down
9 changes: 8 additions & 1 deletion ts/routes/card-info/CardInfo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import CardInfoPlaceholder from "./CardInfoPlaceholder.svelte";
import CardStats from "./CardStats.svelte";
import Revlog from "./Revlog.svelte";
import ForgettingCurve from "./ForgettingCurve.svelte";
export let stats: CardStatsResponse | null = null;
export let showRevlog: boolean = true;
export let fsrsEnabled: boolean = stats?.memoryState != null;
</script>

<Container breakpoint="md" --gutter-inline="1rem" --gutter-block="0.5rem">
Expand All @@ -24,7 +26,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

{#if showRevlog}
<Row>
<Revlog revlog={stats.revlog} />
<Revlog revlog={stats.revlog} {fsrsEnabled} />
</Row>
{/if}
{#if fsrsEnabled}
<Row>
<ForgettingCurve revlog={stats.revlog} />
</Row>
{/if}
{:else}
Expand Down
85 changes: 85 additions & 0 deletions ts/routes/card-info/ForgettingCurve.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { type CardStatsResponse_StatsRevlogEntry as RevlogEntry } from "@generated/anki/stats_pb";
import * as tr from "@generated/ftl";
import Graph from "../graphs/Graph.svelte";
import NoDataOverlay from "../graphs/NoDataOverlay.svelte";
import AxisTicks from "../graphs/AxisTicks.svelte";
import { writable } from "svelte/store";
import InputBox from "../graphs/InputBox.svelte";
import { prepareData, renderForgettingCurve, TimeRange } from "./forgetting-curve";
import { defaultGraphBounds } from "../graphs/graph-helpers";
import HoverColumns from "../graphs/HoverColumns.svelte";
export let revlog: RevlogEntry[];
let svg = null as HTMLElement | SVGElement | null;
const bounds = defaultGraphBounds();
const timeRange = writable(TimeRange.AllTime);
const title = tr.cardStatsFsrsForgettingCurveTitle();
const data = prepareData(revlog, TimeRange.AllTime);
$: renderForgettingCurve(revlog, $timeRange, svg as SVGElement, bounds);
</script>

<div class="forgetting-curve">
<InputBox>
<div class="time-range-selector">
<label>
<input type="radio" bind:group={$timeRange} value={TimeRange.Week} />
{tr.cardStatsFsrsForgettingCurveFirstWeek()}
</label>
<label>
<input type="radio" bind:group={$timeRange} value={TimeRange.Month} />
{tr.cardStatsFsrsForgettingCurveFirstMonth()}
</label>
{#if data.length > 0 && data.some((point) => point.daysSinceFirstLearn > 365)}
<label>
<input
type="radio"
bind:group={$timeRange}
value={TimeRange.Year}
/>
{tr.cardStatsFsrsForgettingCurveFirstYear()}
</label>
{/if}
<label>
<input type="radio" bind:group={$timeRange} value={TimeRange.AllTime} />
{tr.cardStatsFsrsForgettingCurveAllTime()}
</label>
</div>
</InputBox>
<Graph {title}>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
<AxisTicks {bounds} />
<HoverColumns />
<NoDataOverlay {bounds} />
</svg>
</Graph>
</div>

<style>
.forgetting-curve {
width: 100%;
max-width: 50em;
}
.time-range-selector {
display: flex;
justify-content: space-around;
margin-bottom: 1em;
width: 100%;
max-width: 50em;
}
.time-range-selector label {
display: flex;
align-items: center;
}
.time-range-selector input {
margin-right: 0.5em;
}
</style>
32 changes: 30 additions & 2 deletions ts/routes/card-info/Revlog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { RevlogEntry_ReviewKind as ReviewKind } from "@generated/anki/stats_pb";
import * as tr2 from "@generated/ftl";
import { timeSpan, Timestamp } from "@tslib/time";
import { filterRevlogByReviewKind } from "./forgetting-curve";
export let revlog: RevlogEntry[];
export let fsrsEnabled: boolean = false;
function reviewKindClass(entry: RevlogEntry): string {
switch (entry.reviewKind) {
Expand Down Expand Up @@ -54,9 +56,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
interval: string;
ease: string;
takenSecs: string;
elapsedTime: string;
stability: string;
}
function revlogRowFromEntry(entry: RevlogEntry): RevlogRow {
function revlogRowFromEntry(entry: RevlogEntry, elapsedTime: string): RevlogRow {
const timestamp = new Timestamp(Number(entry.time));
return {
Expand All @@ -69,10 +73,33 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
interval: timeSpan(entry.interval),
ease: formatEaseOrDifficulty(entry.ease),
takenSecs: timeSpan(entry.takenSecs, true),
elapsedTime,
stability: entry.memoryState?.stability
? timeSpan(entry.memoryState.stability * 86400)
: "",
};
}
$: revlogRows = revlog.map(revlogRowFromEntry);
$: revlogRows = revlog.map((entry, index) => {
let prevValidEntry: RevlogEntry | undefined;
let i = index + 1;
while (i < revlog.length) {
if (filterRevlogByReviewKind(revlog[i])) {
prevValidEntry = revlog[i];
break;
}
i++;
}
let elapsedTime = "N/A";
if (filterRevlogByReviewKind(entry)) {
elapsedTime = prevValidEntry
? timeSpan(Number(entry.time) - Number(prevValidEntry.time))
: "0";
}
return revlogRowFromEntry(entry, elapsedTime);
});
function formatEaseOrDifficulty(ease: number): string {
if (ease === 0) {
Expand Down Expand Up @@ -145,6 +172,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/each}
</div>
</div>
{#if fsrsEnabled}{/if}
</div>
{/if}

Expand Down
Loading

0 comments on commit c3c89e7

Please sign in to comment.