From 57e3ff9b55a0d645c97033104df36e579cd12054 Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Wed, 3 Jun 2026 22:21:39 +0200 Subject: [PATCH] main: switch the app to use a message sink for UI messages, even though this can be blocking --- src/main.rs | 58 ++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/main.rs b/src/main.rs index a5b21a6..4367aef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,19 +84,22 @@ struct StageEventArgs { #[derive(Debug)] struct App { scene: Scene, + end_time: DateTime, + next_reply_options: Vec, reply_state: ListState, conversation_state: ListState, user_input: Input, - end_time: DateTime, throbber_state: ThrobberState, - prediction_request_sink: watch::Sender, is_requesting: bool, audio_level: f64, recording_audio: bool, - audio_control_sink: watch::Sender, focus_state: FocusState, - tts_request_sink: mpsc::Sender + + audio_control_sink: watch::Sender, + prediction_request_sink: watch::Sender, + tts_request_sink: mpsc::Sender, + sys_message_sink: mpsc::Sender } #[derive(Debug)] @@ -106,7 +109,7 @@ enum FocusState { } impl App { - fn new(prediction_request_sink: watch::Sender, audio_control_sink: watch::Sender, tts_request_sink: mpsc::Sender) -> Self { + fn new(prediction_request_sink: watch::Sender, audio_control_sink: watch::Sender, tts_request_sink: mpsc::Sender, sys_message_sink: mpsc::Sender) -> Self { Self { scene: Scene::default(), next_reply_options: Vec::new(), @@ -121,7 +124,8 @@ impl App { recording_audio: false, audio_control_sink, focus_state: FocusState::UserInput, - tts_request_sink + tts_request_sink, + sys_message_sink } } @@ -355,65 +359,65 @@ impl App { match command { "/bandcamp" => { self.add_bandcamp_artifact(arg).await; - self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Added Bandcamp artifact from {}", arg))); + self.sys_message_sink.send(format!("Added Bandcamp artifact from {}", arg)).await.unwrap(); self.scene.insert_conversation(ConversationEntry::ShipComputer(format!("Incoming transmission."))); }, "/episode" => { if let Ok(episode_number) = arg.trim().parse::() { self.scene.direction.episode_number = episode_number; - self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated episode number: {}", self.scene.direction.episode_number))); + self.sys_message_sink.send(format!("Updated episode number: {}", self.scene.direction.episode_number)).await.unwrap(); self.reload_mixxx_playlist(); } else { - self.scene.insert_conversation(ConversationEntry::SystemMessage("Invalid episode number format. Use /episode [number]".into())); + self.sys_message_sink.send("Invalid episode number format. Use /episode [number]".into()).await.unwrap(); } }, "/reload" => { - self.load(); + self.load().await; self.reload_mixxx_playlist(); }, "/timer" => { if let Ok(minutes) = arg.trim().parse::() { self.end_time = Utc::now() + Duration::minutes(minutes); - self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Set timer for {} minutes.", minutes))); + self.sys_message_sink.send(format!("Set timer for {} minutes.", minutes)).await.unwrap(); } else { - self.scene.insert_conversation(ConversationEntry::SystemMessage("Invalid timer format. Use /timer [minutes]".into())); + self.sys_message_sink.send("Invalid timer format. Use /timer [minutes]".into()).await.unwrap(); } } "/clear" => { match arg.trim() { "playlist" => { self.scene.direction.current_playlist.clear(); - self.scene.insert_conversation(ConversationEntry::SystemMessage("Cleared current playlist.".into())); + self.sys_message_sink.send("Cleared current playlist.".into()).await.unwrap(); return; }, "artifacts" => { self.scene.direction.artifacts.clear(); - self.scene.insert_conversation(ConversationEntry::SystemMessage("Cleared artifacts.".into())); + self.sys_message_sink.send("Cleared artifacts.".into()).await.unwrap(); return; }, "all" => { self.scene = Scene::default(); - self.scene.insert_conversation(ConversationEntry::SystemMessage("Cleared all data.".into())); + self.sys_message_sink.send("Cleared all data.".into()).await.unwrap(); }, "conversation" => { self.scene.conversation.clear(); - self.scene.insert_conversation(ConversationEntry::SystemMessage("Cleared conversation.".into())); + self.sys_message_sink.send("Cleared conversation.".into()).await.unwrap(); }, _ => { - self.scene.insert_conversation(ConversationEntry::SystemMessage("Unknown clear command. Use /clear [playlist|artifacts|all]".into())); + self.sys_message_sink.send("Unknown clear command. Use /clear [playlist|artifacts|all]".into()).await.unwrap(); } } return; }, "/narrative" => { self.scene.direction.narrative = arg.to_string(); - self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated stage direction: {}", self.scene.direction.narrative))); + self.sys_message_sink.send(format!("Updated stage direction: {}", self.scene.direction.narrative)).await.unwrap(); }, "/event" => { self.scene.insert_conversation(ConversationEntry::StageDirection(arg.to_string())); } _ => { - self.scene.insert_conversation(ConversationEntry::SystemMessage("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into())); + self.sys_message_sink.send("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()).await.unwrap(); return; } } @@ -445,15 +449,15 @@ impl App { std::fs::write("save.json", save_data).unwrap(); } - fn load(&mut self) { + async fn load(&mut self) { if let Ok(save_data) = std::fs::read_to_string("save.json") { if let Ok(scene) = serde_json::from_str(&save_data) { self.scene = scene; // FIXME: These should get wiped out when we save as well, or even better, be completely excluded via a custom serde implementation. self.scene.conversation.retain(|line| { if let ConversationEntry::SystemMessage(_) = line { false } else { true }}); - self.scene.insert_conversation(ConversationEntry::SystemMessage("Loaded stored session.".into())); + self.sys_message_sink.send("Loaded stored session.".into()).await.unwrap(); } else { - self.scene.insert_conversation(ConversationEntry::SystemMessage("Failed to load saved session!".into())); + self.sys_message_sink.send("Failed to load saved session!".into()).await.unwrap(); } } } @@ -534,13 +538,13 @@ async fn main() { let mut terminal: Terminal> = ratatui::init(); - let (sys_message_sender, mut sys_message_receiver) = tokio::sync::mpsc::channel(5); + let (sys_message_sink, mut sys_message_src) = tokio::sync::mpsc::channel(32); let tts_request_sender = start_tts().await; let (prediction_request_in, mut prediction_out) = prediction::start_prediction().await; - let (mut audio_state_receiver, audio_control_in, mut transcription_out) = transcription::start_transcription(sys_message_sender).await; + let (mut audio_state_receiver, audio_control_in, mut transcription_out) = transcription::start_transcription(sys_message_sink.clone()).await; - let mut app = App::new(prediction_request_in, audio_control_in, tts_request_sender); - app.load(); + let mut app = App::new(prediction_request_in, audio_control_in, tts_request_sender, sys_message_sink); + app.load().await; let mut events = EventStream::new(); let mut last_tick = Instant::now(); @@ -564,7 +568,7 @@ async fn main() { _ = audio_state_receiver.changed() => { app.audio_level = *audio_state_receiver.borrow_and_update(); }, - maybe_message = sys_message_receiver.recv() => { + maybe_message = sys_message_src.recv() => { if let Some(message) = maybe_message { app.scene.insert_conversation(ConversationEntry::SystemMessage(message)); }