From 6d62fcc096e9565852e1b1901e1673f1361e9539 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 21 Oct 2025 15:05:02 +0100 Subject: [PATCH] next_card_data mediasrv function --- proto/anki/scheduler.proto | 16 +++++++++++--- qt/aqt/mediasrv.py | 34 ++++++++++++++++++++++++++++- rslib/src/card_rendering/service.rs | 2 +- rslib/src/scheduler/service/mod.rs | 20 ++++++++--------- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 4debbbf5a..068dd960c 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -13,6 +13,7 @@ import "anki/decks.proto"; import "anki/collection.proto"; import "anki/config.proto"; import "anki/deck_config.proto"; +import "anki/card_rendering.proto"; service SchedulerService { rpc GetQueuedCards(GetQueuedCardsRequest) returns (QueuedCards); @@ -298,9 +299,18 @@ message NextCardDataResponse { message NextCardData { QueuedCards queue = 1; - string front = 2; - string back = 3; - repeated AnswerButton answer_buttons = 4; + repeated AnswerButton answer_buttons = 2; + + // TODO: We can probably make this a little faster by using oneof and + // preventing the partial_front and back being sent to svelte where it isn't + // used Alternatively we can use a completely different message for both + // Rust -> Python and the Python -> Svelte though this would be more + // complicated to implement. + string front = 3; + string back = 4; + + repeated card_rendering.RenderedTemplateNode partial_front = 5; + repeated card_rendering.RenderedTemplateNode partial_back = 6; } optional NextCardData next_card = 1; diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index bdca3dce3..2a5bbbd52 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -28,9 +28,16 @@ import aqt import aqt.main import aqt.operations from anki import hooks +from anki.cards import Card from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest +from anki.scheduler_pb2 import NextCardDataResponse +from anki.template import ( + PartiallyRenderedCard, + TemplateRenderContext, + apply_custom_filters, +) from anki.utils import dev_mode from aqt.changenotetype import ChangeNotetypeDialog from aqt.deckoptions import DeckOptionsDialog @@ -638,6 +645,31 @@ def save_custom_colours() -> bytes: return b"" +def next_card_data() -> bytes: + raw = aqt.mw.col._backend.next_card_data_raw(request.data) + data = NextCardDataResponse.FromString(raw) + backend_card = data.next_card.queue.cards[0].card + card = Card(aqt.mw.col, backend_card=backend_card) + + ctx = TemplateRenderContext.from_existing_card(card, False) + + qside = apply_custom_filters( + PartiallyRenderedCard.nodes_from_proto(data.next_card.partial_front), + ctx, + None, + ) + aside = apply_custom_filters( + PartiallyRenderedCard.nodes_from_proto(data.next_card.partial_back), + ctx, + qside, + ) + + data.next_card.front = qside + data.next_card.back = aside + + return data.SerializeToString() + + post_handler_list = [ congrats_info, get_deck_configs_for_update, @@ -654,6 +686,7 @@ post_handler_list = [ deck_options_require_close, deck_options_ready, save_custom_colours, + next_card_data, ] @@ -696,7 +729,6 @@ exposed_backend_list = [ "get_optimal_retention_parameters", "simulate_fsrs_review", "simulate_fsrs_workload", - "next_card_data", # DeckConfigService "get_ignored_before_count", "get_retention_workload", diff --git a/rslib/src/card_rendering/service.rs b/rslib/src/card_rendering/service.rs index 73f8302ca..f9461821d 100644 --- a/rslib/src/card_rendering/service.rs +++ b/rslib/src/card_rendering/service.rs @@ -180,7 +180,7 @@ impl crate::services::CardRenderingService for Collection { } } -fn rendered_nodes_to_proto( +pub(crate) fn rendered_nodes_to_proto( nodes: Vec, ) -> Vec { nodes diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index 1bcc35b68..294e4e4ac 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -27,6 +27,7 @@ use fsrs::FSRSReview; use fsrs::FSRS; use crate::backend::Backend; +use crate::card_rendering::service::rendered_nodes_to_proto; use crate::prelude::*; use crate::scheduler::fsrs::params::ComputeParamsRequest; use crate::scheduler::new::NewCardDueOrder; @@ -34,7 +35,6 @@ use crate::scheduler::states::CardState; use crate::scheduler::states::SchedulingStates; use crate::search::SortMode; use crate::stats::studied_today; -use crate::text::encode_iri_paths; impl crate::services::SchedulerService for Collection { /// This behaves like _updateCutoff() in older code - it also unburies at @@ -397,8 +397,8 @@ impl crate::services::SchedulerService for Collection { if let Some(next_card) = next_card { let cid = next_card.card.id; - let render = self.render_existing_card(cid, false, false)?; - let style = format!("", render.css); + let render = self.render_existing_card(cid, false, true)?; + //let style = format!("", render.css); let answer_buttons = self .describe_next_states(&next_card.states)? @@ -410,17 +410,15 @@ impl crate::services::SchedulerService for Collection { }) .collect(); - let prepare_card_text_for_display = |html: &str| { - let html = [style.clone(), html.to_string()].concat(); - let html = encode_iri_paths(&html).to_string(); - html - }; - Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), - front: prepare_card_text_for_display(&render.question()), - back: prepare_card_text_for_display(&render.answer()), + + front: "".to_string(), + back: "".to_string(), + + partial_front: rendered_nodes_to_proto(render.qnodes), + partial_back: rendered_nodes_to_proto(render.anodes), answer_buttons, }),