diff --git a/Cargo.lock b/Cargo.lock index 7f1ca5f..f4d3631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1007,6 +1007,7 @@ dependencies = [ "sqlite", "static-iref", "tempfile", + "textwrap", "throbber-widgets-tui", "tokio", "tui-input", @@ -3941,6 +3942,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "snafu" version = "0.8.9" @@ -4305,6 +4312,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4667,6 +4685,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-segmentation" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index 8204c09..d0c9010 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ serde_json = "1.0.150" sqlite = "0.37.0" static-iref = "3.0.0" tempfile = "3.27.0" +textwrap = "0.16.2" throbber-widgets-tui = "0.11.0" tokio = { version = "1.52.3", features = ["full"] } tui-input = "0.15.3" diff --git a/src/main.rs b/src/main.rs index 231ce16..b09d2c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -312,6 +312,65 @@ impl App { self.regenerate_responses(); } + async fn on_command(&mut self, command: &str) { + let mut parts = command.splitn(2, " "); + let command = parts.next().unwrap(); + let arg = parts.next().unwrap_or(""); + match command { + "/bandcamp" => { + self.add_bandcamp_artifact(arg).await; + self.sys_message_sink.send(format!("Added Bandcamp artifact from {}", arg)).await.unwrap(); + self.next_actions.push(ConversationEntry::ShipComputer(format!("Incoming transmission from {}", arg))); + self.regenerate_responses(); + }, + "/episode" => { + if let Ok(episode_number) = arg.trim().parse::() { + self.direction.episode_number = episode_number; + self.sys_message_sink.send(format!("Updated episode number: {}", self.direction.episode_number)).await.unwrap(); + self.reload_mixxx_playlist(); + } else { + self.sys_message_sink.send("Invalid episode number format. Use /episode [number]".into()).await.unwrap(); + return; + } + }, + "/timer" => { + if let Ok(minutes) = arg.trim().parse::() { + self.end_time = Utc::now() + Duration::minutes(minutes); + self.sys_message_sink.send(format!("Set timer for {} minutes.", minutes)).await.unwrap(); + } else { + self.sys_message_sink.send("Invalid timer format. Use /timer [minutes]".into()).await.unwrap(); + } + }, + "/clear" => { + match arg.trim() { + "artifacts" => { + self.direction.artifacts.clear(); + self.sys_message_sink.send("Cleared artifacts.".into()).await.unwrap(); + }, + _ => { + self.sys_message_sink.send("Unknown clear command. Use /clear [artifacts]".into()).await.unwrap(); + } + } + return; + }, + "/narrative" => { + self.direction.narrative = arg.to_string(); + self.sys_message_sink.send(format!("Updated stage direction: {}", self.direction.narrative)).await.unwrap(); + }, + "/event" => { + self.next_actions.push(ConversationEntry::StageDirection(arg.to_string())); + self.regenerate_responses(); + }, + "/computer" => { + self.next_actions.push(ConversationEntry::ShipComputer(arg.to_string())); + self.regenerate_responses(); + }, + _ => { + self.sys_message_sink.send("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()).await.unwrap(); + } + } + } + async fn on_event(&mut self, evt: event::Event) { if let Some(key) = evt.as_key_press_event() { match self.focus_state { @@ -366,70 +425,7 @@ impl App { self.insert_selected_prompt().await; } else { if next_msg.starts_with("/") { - let mut parts = next_msg.splitn(2, " "); - let command = parts.next().unwrap(); - let arg = parts.next().unwrap_or(""); - match command { - "/bandcamp" => { - self.add_bandcamp_artifact(arg).await; - self.sys_message_sink.send(format!("Added Bandcamp artifact from {}", arg)).await.unwrap(); - self.next_actions.push(ConversationEntry::ShipComputer(format!("Incoming transmission from {}", arg))); - self.regenerate_responses(); - }, - "/episode" => { - if let Ok(episode_number) = arg.trim().parse::() { - self.direction.episode_number = episode_number; - self.sys_message_sink.send(format!("Updated episode number: {}", self.direction.episode_number)).await.unwrap(); - self.reload_mixxx_playlist(); - } else { - self.sys_message_sink.send("Invalid episode number format. Use /episode [number]".into()).await.unwrap(); - return; - } - }, - "/timer" => { - if let Ok(minutes) = arg.trim().parse::() { - self.end_time = Utc::now() + Duration::minutes(minutes); - self.sys_message_sink.send(format!("Set timer for {} minutes.", minutes)).await.unwrap(); - } else { - self.sys_message_sink.send("Invalid timer format. Use /timer [minutes]".into()).await.unwrap(); - } - }, - "/clear" => { - match arg.trim() { - "playlist" => { - self.direction.current_playlist.clear(); - self.sys_message_sink.send("Cleared current playlist.".into()).await.unwrap(); - }, - "artifacts" => { - self.direction.artifacts.clear(); - self.sys_message_sink.send("Cleared artifacts.".into()).await.unwrap(); - }, - "all" => { - self.scene = Scene::default(); - self.sys_message_sink.send("Cleared all data.".into()).await.unwrap(); - }, - _ => { - self.sys_message_sink.send("Unknown clear command. Use /clear [playlist|artifacts|all]".into()).await.unwrap(); - } - } - return; - }, - "/narrative" => { - self.direction.narrative = arg.to_string(); - self.sys_message_sink.send(format!("Updated stage direction: {}", self.direction.narrative)).await.unwrap(); - }, - "/event" => { - self.next_actions.push(ConversationEntry::StageDirection(arg.to_string())); - self.regenerate_responses(); - }, - "/computer" => { - self.next_actions.push(ConversationEntry::ShipComputer(arg.to_string())); - self.regenerate_responses(); - }, - _ => { - self.sys_message_sink.send("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()).await.unwrap(); - } - } + self.on_command(&next_msg).await; } else { self.next_actions.push(ConversationEntry::User(next_msg)); self.regenerate_responses(); @@ -528,16 +524,23 @@ async fn main() { return; } - let saved_session = if let Ok(save_data) = std::fs::read_to_string("save.json") { - serde_json::from_str(&save_data).unwrap_or_default() - //FIXME: Re-add load messages to sys log - } else { - SaveData::default() - }; - let mut terminal: Terminal> = ratatui::init(); let (sys_message_sink, sys_message_src) = tokio::sync::mpsc::channel(32); + + let saved_session = if let Ok(save_data) = std::fs::read_to_string("save.json") { + if let Ok(ret) = serde_json::from_str(&save_data) { + sys_message_sink.send("Loaded session from save.json".into()).await.unwrap(); + ret + } else { + sys_message_sink.send("Could not load saved session!".into()).await.unwrap(); + SaveData::default() + } + } else { + sys_message_sink.send("Creating new session in save.json".into()).await.unwrap(); + SaveData::default() + }; + let tts_request_sender = start_tts().await; let (prediction_request_in, mut prediction_out) = prediction::start_prediction(sys_message_src, saved_session.messages).await; let (mut audio_state_receiver, audio_control_in, mut transcription_out) = transcription::start_transcription(sys_message_sink.clone()).await; diff --git a/src/scene.rs b/src/scene.rs index 78100f3..4999a68 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -56,16 +56,6 @@ pub struct StageDirection { pub current_playlist: Vec } -/*impl StageDirection { - pub fn insert_conversation(&mut self, entry: ConversationEntry) { - self.additions.push(entry); - } - - pub fn take_actions(&mut self) -> StageActions { - StageActions { direction: self.clone(), additions: std::mem::take(&mut self.additions) } - } -}*/ - #[derive(Debug, Default, Clone)] pub struct StageActions { pub direction: StageDirection, diff --git a/src/system-prompt.txt b/src/system-prompt.txt index a591962..0f17a2b 100644 --- a/src/system-prompt.txt +++ b/src/system-prompt.txt @@ -45,7 +45,7 @@ It will also report out ship conditions, such as incoming transmissions, status # Constraints In a subsequent system prompt, you will be given the currrent 'stage direction' of the show, which includes the current playtime, the number of the episode, and any particular extra information about this episode that you should be aware of. The stage direction is provided as structured JSON. There may be additional data fields for semantic context that should be incorporated into the roleplaying setting. -A list of artifacts that will be encountered during the episode are provided as blobs of json+ld metadata. +A list of artifacts that will be encountered during the episode are provided as blobs of json metadata. Additionally, the current playlist of the radio show can be found as an array of track data. Your response will be used verbatim to generate speach using a text-to-speech engine, meaning you should not include any tone indicators or other formatting. This also means that your responses must not refer to any "lore", or "show", these instructions, or anything else out of character.