Merge branch 'main' into Refactor-reviews_for_fsrs-function-for-improved-performance

This commit is contained in:
Jarrett Ye 2025-09-17 13:43:13 +08:00 committed by GitHub
commit 1afc7e35ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 94 additions and 28 deletions

View file

@ -49,6 +49,7 @@ Sander Santema <github.com/sandersantema/>
Thomas Brownback <https://github.com/brownbat/> Thomas Brownback <https://github.com/brownbat/>
Andrew Gaul <andrew@gaul.org> Andrew Gaul <andrew@gaul.org>
kenden kenden
Emil Hamrin <github.com/e-hamrin>
Nickolay Yudin <kelciour@gmail.com> Nickolay Yudin <kelciour@gmail.com>
neitrinoweb <github.com/neitrinoweb/> neitrinoweb <github.com/neitrinoweb/>
Andreas Reis <github.com/nwwt> Andreas Reis <github.com/nwwt>

View file

@ -1,35 +1,78 @@
# This Dockerfile uses three stages. # This is a user-contributed Dockerfile. No official support is available.
# 1. Compile anki (and dependencies) and build python wheels.
# 2. Create a virtual environment containing anki and its dependencies.
# 3. Create a final image that only includes anki's virtual environment and required
# system packages.
ARG PYTHON_VERSION="3.9"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
# Build anki. FROM ubuntu:24.04 AS build
FROM python:$PYTHON_VERSION AS build
RUN curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.7.4/bazelisk-linux-amd64 \
> /usr/local/bin/bazel \
&& chmod +x /usr/local/bin/bazel \
# Bazel expects /usr/bin/python
&& ln -s /usr/local/bin/python /usr/bin/python
WORKDIR /opt/anki WORKDIR /opt/anki
COPY . . ENV PYTHON_VERSION="3.13"
# Build python wheels.
# System deps
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
build-essential \
pkg-config \
libssl-dev \
libbz2-dev \
libreadline-dev \
libsqlite3-dev \
libffi-dev \
zlib1g-dev \
liblzma-dev \
ca-certificates \
ninja-build \
rsync \
libglib2.0-0 \
libgl1 \
libx11-6 \
libxext6 \
libxrender1 \
libxkbcommon0 \
libxkbcommon-x11-0 \
libxcb1 \
libxcb-render0 \
libxcb-shm0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-shape0 \
libxcb-xfixes0 \
libxcb-xinerama0 \
libxcb-xinput0 \
libsm6 \
libice6 \
&& rm -rf /var/lib/apt/lists/*
# install rust with rustup
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Install uv and Python 3.13 with uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
&& ln -s /root/.local/bin/uv /usr/local/bin/uv
ENV PATH="/root/.local/bin:${PATH}"
RUN uv python install ${PYTHON_VERSION} --default
COPY . .
RUN ./tools/build RUN ./tools/build
# Install pre-compiled Anki. # Install pre-compiled Anki.
FROM python:${PYTHON_VERSION}-slim as installer FROM python:3.13-slim AS installer
WORKDIR /opt/anki/ WORKDIR /opt/anki/
COPY --from=build /opt/anki/wheels/ wheels/ COPY --from=build /opt/anki/out/wheels/ wheels/
# Use virtual environment. # Use virtual environment.
RUN python -m venv venv \ RUN python -m venv venv \
&& ./venv/bin/python -m pip install --no-cache-dir setuptools wheel \ && ./venv/bin/python -m pip install --no-cache-dir setuptools wheel \
&& ./venv/bin/python -m pip install --no-cache-dir /opt/anki/wheels/*.whl && ./venv/bin/python -m pip install --no-cache-dir /opt/anki/wheels/*.whl
# We use another build stage here so we don't include the wheels in the final image. # We use another build stage here so we don't include the wheels in the final image.
FROM python:${PYTHON_VERSION}-slim as final FROM python:3.13-slim AS final
COPY --from=installer /opt/anki/venv /opt/anki/venv COPY --from=installer /opt/anki/venv /opt/anki/venv
ENV PATH=/opt/anki/venv/bin:$PATH ENV PATH=/opt/anki/venv/bin:$PATH
# Install run-time dependencies. # Install run-time dependencies.
@ -59,9 +102,9 @@ RUN apt-get update \
libxrender1 \ libxrender1 \
libxtst6 \ libxtst6 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Add non-root user. # Add non-root user.
RUN useradd --create-home anki RUN useradd --create-home anki
USER anki USER anki
WORKDIR /work WORKDIR /work
ENTRYPOINT ["/opt/anki/venv/bin/anki"] ENTRYPOINT ["/opt/anki/venv/bin/anki"]
LABEL maintainer="Jakub Kaczmarzyk <jakub.kaczmarzyk@gmail.com>"

View file

@ -46,6 +46,20 @@ studying-type-answer-unknown-field = Type answer: unknown field { $val }
studying-unbury = Unbury studying-unbury = Unbury
studying-what-would-you-like-to-unbury = What would you like to unbury? studying-what-would-you-like-to-unbury = What would you like to unbury?
studying-you-havent-recorded-your-voice-yet = You haven't recorded your voice yet. studying-you-havent-recorded-your-voice-yet = You haven't recorded your voice yet.
studying-card-studied-in-minute =
{ $cards ->
[one] { $cards } card
*[other] { $cards } cards
} studied in
{ $minutes ->
[one] { $minutes } minute.
*[other] { $minutes } minutes.
}
studying-question-time-elapsed = Question time elapsed
studying-answer-time-elapsed = Answer time elapsed
## OBSOLETE; you do not need to translate this
studying-card-studied-in = studying-card-studied-in =
{ $count -> { $count ->
[one] { $count } card studied in [one] { $count } card studied in
@ -56,5 +70,3 @@ studying-minute =
[one] { $count } minute. [one] { $count } minute.
*[other] { $count } minutes. *[other] { $count } minutes.
} }
studying-question-time-elapsed = Question time elapsed
studying-answer-time-elapsed = Answer time elapsed

View file

@ -13,7 +13,7 @@ import aqt.browser
from anki.cards import Card from anki.cards import Card
from anki.collection import Config from anki.collection import Config
from anki.tags import MARKED_TAG from anki.tags import MARKED_TAG
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks, is_mac
from aqt.qt import ( from aqt.qt import (
QCheckBox, QCheckBox,
QDialog, QDialog,
@ -81,10 +81,15 @@ class Previewer(QDialog):
qconnect(self.finished, self._on_finished) qconnect(self.finished, self._on_finished)
self.silentlyClose = True self.silentlyClose = True
self.vbox = QVBoxLayout() self.vbox = QVBoxLayout()
spacing = 6
self.vbox.setContentsMargins(0, 0, 0, 0) self.vbox.setContentsMargins(0, 0, 0, 0)
self.vbox.setSpacing(spacing)
self._web: AnkiWebView | None = AnkiWebView(kind=AnkiWebViewKind.PREVIEWER) self._web: AnkiWebView | None = AnkiWebView(kind=AnkiWebViewKind.PREVIEWER)
self.vbox.addWidget(self._web) self.vbox.addWidget(self._web)
self.bbox = QDialogButtonBox() self.bbox = QDialogButtonBox()
self.bbox.setContentsMargins(
spacing, spacing if is_mac else 0, spacing, spacing
)
self.bbox.setLayoutDirection(Qt.LayoutDirection.LeftToRight) self.bbox.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
gui_hooks.card_review_webview_did_init(self._web, AnkiWebViewKind.PREVIEWER) gui_hooks.card_review_webview_did_init(self._web, AnkiWebViewKind.PREVIEWER)

View file

@ -17,6 +17,7 @@ import aqt.browser
import aqt.operations import aqt.operations
from anki.cards import Card, CardId from anki.cards import Card, CardId
from anki.collection import Config, OpChanges, OpChangesWithCount from anki.collection import Config, OpChanges, OpChangesWithCount
from anki.lang import with_collapsed_whitespace
from anki.scheduler.base import ScheduleCardsAsNew from anki.scheduler.base import ScheduleCardsAsNew
from anki.scheduler.v3 import ( from anki.scheduler.v3 import (
CardAnswer, CardAnswer,
@ -966,11 +967,15 @@ timerStopped = false;
elapsed = self.mw.col.timeboxReached() elapsed = self.mw.col.timeboxReached()
if elapsed: if elapsed:
assert not isinstance(elapsed, bool) assert not isinstance(elapsed, bool)
part1 = tr.studying_card_studied_in(count=elapsed[1]) cards_val = elapsed[1]
mins = int(round(elapsed[0] / 60)) minutes_val = int(round(elapsed[0] / 60))
part2 = tr.studying_minute(count=mins) message = with_collapsed_whitespace(
tr.studying_card_studied_in_minute(
cards=cards_val, minutes=str(minutes_val)
)
)
fin = tr.studying_finish() fin = tr.studying_finish()
diag = askUserDialog(f"{part1} {part2}", [tr.studying_continue(), fin]) diag = askUserDialog(message, [tr.studying_continue(), fin])
diag.setIcon(QMessageBox.Icon.Information) diag.setIcon(QMessageBox.Icon.Information)
if diag.run() == fin: if diag.run() == fin:
self.mw.moveToState("deckBrowser") self.mw.moveToState("deckBrowser")

View file

@ -174,7 +174,7 @@ impl Collection {
} }
} }
let health_check_passed = if health_check { let health_check_passed = if health_check && input.train_set.len() > 300 {
let fsrs = FSRS::new(None)?; let fsrs = FSRS::new(None)?;
fsrs.evaluate_with_time_series_splits(input, |_| true) fsrs.evaluate_with_time_series_splits(input, |_| true)
.ok() .ok()