main: implement the ability to replay eva utterances via list selection
This commit is contained in:
+110
-60
@@ -81,6 +81,13 @@ struct App {
|
||||
audio_level: f64,
|
||||
recording_audio: bool,
|
||||
audio_control_sink: tokio::sync::mpsc::Sender<AudioRecordRequest>,
|
||||
focus_state: FocusState
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FocusState {
|
||||
Conversation,
|
||||
UserInput
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -89,6 +96,7 @@ impl App {
|
||||
scene: Scene::default(),
|
||||
next_reply_options: Vec::new(),
|
||||
reply_state: ListState::default(),
|
||||
conversation_state: ListState::default(),
|
||||
user_input: Input::default(),
|
||||
end_time: Utc::now() + Duration::hours(2),
|
||||
throbber_state: ThrobberState::default(),
|
||||
@@ -97,10 +105,11 @@ impl App {
|
||||
audio_level: -60.,
|
||||
recording_audio: false,
|
||||
audio_control_sink,
|
||||
focus_state: FocusState::UserInput
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_conversation(&self, frame: &mut Frame, area: Rect) {
|
||||
fn draw_conversation(&mut self, frame: &mut Frame, area: Rect) {
|
||||
let items: Vec<Line> = self.scene.conversation.iter().rev().map(|entry| {
|
||||
match entry {
|
||||
ConversationEntry::User(text) => Line::from_iter([Span::from("Argee: ").style(ratatui::style::Color::Magenta), Span::from(text)]),
|
||||
@@ -110,7 +119,17 @@ impl App {
|
||||
ConversationEntry::SystemMessage(text) => Line::from_iter([text]).style(ratatui::style::Color::DarkGray)
|
||||
}
|
||||
}).collect();
|
||||
frame.render_widget(List::new(items).block(Block::bordered().border_style(style::Color::LightYellow).title("Conversation")).direction(ListDirection::BottomToTop), area);
|
||||
// FIXME: We need to somehow make long list items wrap. https://github.com/ratatui/ratatui/issues/128#issuecomment-1613918499
|
||||
// TODO: Would be nice to be able to scroll a longer conversation with the scroll wheel, or with page up/down
|
||||
frame.render_stateful_widget(
|
||||
List::new(items)
|
||||
.block(Block::bordered().border_style(style::Color::LightYellow).title("Conversation (Press Tab to select and read Eva's lines aloud)"))
|
||||
.direction(ListDirection::BottomToTop)
|
||||
.highlight_symbol("> ")
|
||||
.highlight_style(style::Style::new().bold().fg(style::Color::Cyan)),
|
||||
area,
|
||||
&mut self.conversation_state
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_options(&mut self, frame: &mut Frame, area: Rect) {
|
||||
@@ -261,67 +280,98 @@ impl App {
|
||||
|
||||
async fn on_event(&mut self, evt: event::Event) {
|
||||
if let Some(key) = evt.as_key_press_event() {
|
||||
match key.code {
|
||||
KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => self.regenerate_responses(),
|
||||
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
if self.recording_audio {
|
||||
self.recording_audio = false;
|
||||
self.audio_control_sink.send(AudioRecordRequest::Finish).await.unwrap();
|
||||
self.is_requesting = true;
|
||||
} else {
|
||||
self.recording_audio = true;
|
||||
self.audio_control_sink.send(AudioRecordRequest::Start).await.unwrap();
|
||||
}
|
||||
},
|
||||
KeyCode::Down => self.reply_state.select_next(),
|
||||
KeyCode::Up => self.reply_state.select_previous(),
|
||||
KeyCode::Tab => {
|
||||
self.insert_selected_prompt();
|
||||
},
|
||||
KeyCode::Enter => {
|
||||
let next_msg = self.user_input.value_and_reset();
|
||||
if next_msg.trim().is_empty() {
|
||||
self.insert_selected_prompt();
|
||||
} 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.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Added Bandcamp artifact from {}", arg)));
|
||||
},
|
||||
"/episode" => {
|
||||
if let Ok(episode_number) = arg.trim().parse::<u32>() {
|
||||
self.scene.direction.episode_number = episode_number;
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated episode number: {}", self.scene.direction.episode_number)));
|
||||
self.reload_mixxx_playlist();
|
||||
} else {
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage("Invalid episode number format. Use /episode [number]".into()));
|
||||
}
|
||||
},
|
||||
"/reset" => {
|
||||
self.scene = Scene::default();
|
||||
return;
|
||||
},
|
||||
"/narrative" => {
|
||||
self.scene.direction.narrative = arg.to_string();
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated stage direction: {}", self.scene.direction.narrative)));
|
||||
},
|
||||
_ => {
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()));
|
||||
return;
|
||||
}
|
||||
match self.focus_state {
|
||||
FocusState::Conversation => {
|
||||
match key.code {
|
||||
KeyCode::Tab => {
|
||||
self.focus_state = FocusState::UserInput;
|
||||
self.conversation_state.select(None);
|
||||
},
|
||||
KeyCode::PageUp => self.conversation_state.scroll_down_by(5),
|
||||
KeyCode::PageDown => self.conversation_state.scroll_up_by(5),
|
||||
KeyCode::Home => self.conversation_state.select_last(),
|
||||
KeyCode::End => self.conversation_state.select_first(),
|
||||
KeyCode::Down => self.conversation_state.select_previous(),
|
||||
KeyCode::Up => self.conversation_state.select_next(),
|
||||
KeyCode::Enter => {
|
||||
let row_num = self.conversation_state.selected().unwrap();
|
||||
if let ConversationEntry::Eva(text) = &self.scene.conversation[self.scene.conversation.len() - 1 - row_num] {
|
||||
self.speak(text.clone().as_str());
|
||||
self.focus_state = FocusState::UserInput;
|
||||
self.conversation_state.select(None);
|
||||
}
|
||||
} else {
|
||||
self.scene.insert_conversation(ConversationEntry::User(next_msg));
|
||||
}
|
||||
self.save();
|
||||
self.regenerate_responses();
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
},
|
||||
_ => {self.user_input.handle_event(&evt);},
|
||||
FocusState::UserInput => {
|
||||
match key.code {
|
||||
KeyCode::Tab => {
|
||||
self.focus_state = FocusState::Conversation;
|
||||
self.conversation_state.select_first();
|
||||
},
|
||||
KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => self.regenerate_responses(),
|
||||
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
if self.recording_audio {
|
||||
self.recording_audio = false;
|
||||
self.audio_control_sink.send(AudioRecordRequest::Finish).await.unwrap();
|
||||
self.is_requesting = true;
|
||||
} else {
|
||||
self.recording_audio = true;
|
||||
self.audio_control_sink.send(AudioRecordRequest::Start).await.unwrap();
|
||||
}
|
||||
},
|
||||
KeyCode::Down => self.reply_state.select_next(),
|
||||
KeyCode::Up => self.reply_state.select_previous(),
|
||||
KeyCode::Enter if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
self.insert_selected_prompt();
|
||||
},
|
||||
KeyCode::Enter => {
|
||||
let next_msg = self.user_input.value_and_reset();
|
||||
if next_msg.trim().is_empty() {
|
||||
self.insert_selected_prompt();
|
||||
} 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.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Added Bandcamp artifact from {}", arg)));
|
||||
},
|
||||
"/episode" => {
|
||||
if let Ok(episode_number) = arg.trim().parse::<u32>() {
|
||||
self.scene.direction.episode_number = episode_number;
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated episode number: {}", self.scene.direction.episode_number)));
|
||||
self.reload_mixxx_playlist();
|
||||
} else {
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage("Invalid episode number format. Use /episode [number]".into()));
|
||||
}
|
||||
},
|
||||
"/reset" => {
|
||||
self.scene = Scene::default();
|
||||
return;
|
||||
},
|
||||
"/narrative" => {
|
||||
self.scene.direction.narrative = arg.to_string();
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage(format!("Updated stage direction: {}", self.scene.direction.narrative)));
|
||||
},
|
||||
_ => {
|
||||
self.scene.insert_conversation(ConversationEntry::SystemMessage("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.scene.insert_conversation(ConversationEntry::User(next_msg));
|
||||
}
|
||||
self.save();
|
||||
self.regenerate_responses();
|
||||
}
|
||||
},
|
||||
_ => {self.user_input.handle_event(&evt);},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user