main: implement the ability to replay eva utterances via list selection
This commit is contained in:
+53
-3
@@ -81,6 +81,13 @@ struct App {
|
|||||||
audio_level: f64,
|
audio_level: f64,
|
||||||
recording_audio: bool,
|
recording_audio: bool,
|
||||||
audio_control_sink: tokio::sync::mpsc::Sender<AudioRecordRequest>,
|
audio_control_sink: tokio::sync::mpsc::Sender<AudioRecordRequest>,
|
||||||
|
focus_state: FocusState
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FocusState {
|
||||||
|
Conversation,
|
||||||
|
UserInput
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -89,6 +96,7 @@ impl App {
|
|||||||
scene: Scene::default(),
|
scene: Scene::default(),
|
||||||
next_reply_options: Vec::new(),
|
next_reply_options: Vec::new(),
|
||||||
reply_state: ListState::default(),
|
reply_state: ListState::default(),
|
||||||
|
conversation_state: ListState::default(),
|
||||||
user_input: Input::default(),
|
user_input: Input::default(),
|
||||||
end_time: Utc::now() + Duration::hours(2),
|
end_time: Utc::now() + Duration::hours(2),
|
||||||
throbber_state: ThrobberState::default(),
|
throbber_state: ThrobberState::default(),
|
||||||
@@ -97,10 +105,11 @@ impl App {
|
|||||||
audio_level: -60.,
|
audio_level: -60.,
|
||||||
recording_audio: false,
|
recording_audio: false,
|
||||||
audio_control_sink,
|
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| {
|
let items: Vec<Line> = self.scene.conversation.iter().rev().map(|entry| {
|
||||||
match entry {
|
match entry {
|
||||||
ConversationEntry::User(text) => Line::from_iter([Span::from("Argee: ").style(ratatui::style::Color::Magenta), Span::from(text)]),
|
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)
|
ConversationEntry::SystemMessage(text) => Line::from_iter([text]).style(ratatui::style::Color::DarkGray)
|
||||||
}
|
}
|
||||||
}).collect();
|
}).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) {
|
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) {
|
async fn on_event(&mut self, evt: event::Event) {
|
||||||
if let Some(key) = evt.as_key_press_event() {
|
if let Some(key) = evt.as_key_press_event() {
|
||||||
|
match self.focus_state {
|
||||||
|
FocusState::Conversation => {
|
||||||
match key.code {
|
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('r') if key.modifiers.contains(KeyModifiers::CONTROL) => self.regenerate_responses(),
|
||||||
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
if self.recording_audio {
|
if self.recording_audio {
|
||||||
@@ -275,7 +323,7 @@ impl App {
|
|||||||
},
|
},
|
||||||
KeyCode::Down => self.reply_state.select_next(),
|
KeyCode::Down => self.reply_state.select_next(),
|
||||||
KeyCode::Up => self.reply_state.select_previous(),
|
KeyCode::Up => self.reply_state.select_previous(),
|
||||||
KeyCode::Tab => {
|
KeyCode::Enter if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
self.insert_selected_prompt();
|
self.insert_selected_prompt();
|
||||||
},
|
},
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
@@ -325,6 +373,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn add_bandcamp_artifact(&mut self, url: &str) {
|
async fn add_bandcamp_artifact(&mut self, url: &str) {
|
||||||
let body = reqwest::get(url).await.unwrap().text().await.unwrap();
|
let body = reqwest::get(url).await.unwrap().text().await.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user