main: implement the ability to replay eva utterances via list selection

This commit is contained in:
2026-06-02 11:28:25 +02:00
parent 5579b4dc64
commit 326817733a
+53 -3
View File
@@ -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,7 +280,36 @@ impl App {
async fn on_event(&mut self, evt: event::Event) {
if let Some(key) = evt.as_key_press_event() {
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);
}
},
_ => ()
}
},
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 {
@@ -275,7 +323,7 @@ impl App {
},
KeyCode::Down => self.reply_state.select_next(),
KeyCode::Up => self.reply_state.select_previous(),
KeyCode::Tab => {
KeyCode::Enter if key.modifiers.contains(KeyModifiers::CONTROL) => {
self.insert_selected_prompt();
},
KeyCode::Enter => {
@@ -325,6 +373,8 @@ impl App {
}
}
}
}
}
async fn add_bandcamp_artifact(&mut self, url: &str) {
let body = reqwest::get(url).await.unwrap().text().await.unwrap();