diff options
Diffstat (limited to 'crates/atuin-ai/src/tui/components')
| -rw-r--r-- | crates/atuin-ai/src/tui/components/input_box.rs | 16 | ||||
| -rw-r--r-- | crates/atuin-ai/src/tui/components/mod.rs | 1 | ||||
| -rw-r--r-- | crates/atuin-ai/src/tui/components/session_continue.rs | 49 |
3 files changed, 65 insertions, 1 deletions
diff --git a/crates/atuin-ai/src/tui/components/input_box.rs b/crates/atuin-ai/src/tui/components/input_box.rs index f5e0fe2b..6e041418 100644 --- a/crates/atuin-ai/src/tui/components/input_box.rs +++ b/crates/atuin-ai/src/tui/components/input_box.rs @@ -19,7 +19,7 @@ use ratatui_core::{ }; use tui_textarea::TextArea; -use crate::tui::events::AiTuiEvent; +use crate::tui::{events::AiTuiEvent, slash::SlashCommandSearchResult}; /// A bordered text input box backed by tui-textarea. /// @@ -35,6 +35,8 @@ pub(crate) struct InputBox { pub footer: String, /// Whether the input is currently active (shows cursor, accepts input) pub active: bool, + /// If the user has typed a slash command, this holds the best match for it. + pub slash_suggestion: Option<SlashCommandSearchResult>, } pub(crate) struct InputBoxState { @@ -129,6 +131,18 @@ fn input_box( textarea.insert_newline(); return EventResult::Consumed; } + crossterm::event::KeyCode::Tab if props.slash_suggestion.is_some() => { + // If there's a slash command suggestion, Tab accepts it. + if let Some(suggestion) = &props.slash_suggestion { + textarea.clear(); + textarea.insert_str(format!("/{}", suggestion.command.name)); + // Manually trigger an input update event so the slash suggestion box can update immediately + if let Some(ref tx) = state.tx { + let _ = tx.send(AiTuiEvent::InputUpdated(textarea.lines().join("\n"))); + } + return EventResult::Consumed; + } + } crossterm::event::KeyCode::Enter => { if key.modifiers.contains(KeyModifiers::SHIFT) { textarea.insert_newline(); diff --git a/crates/atuin-ai/src/tui/components/mod.rs b/crates/atuin-ai/src/tui/components/mod.rs index 3458327d..9959dbad 100644 --- a/crates/atuin-ai/src/tui/components/mod.rs +++ b/crates/atuin-ai/src/tui/components/mod.rs @@ -2,3 +2,4 @@ pub(crate) mod atuin_ai; pub(crate) mod input_box; pub(crate) mod markdown; pub(crate) mod select; +pub(crate) mod session_continue; diff --git a/crates/atuin-ai/src/tui/components/session_continue.rs b/crates/atuin-ai/src/tui/components/session_continue.rs new file mode 100644 index 00000000..bfbfb191 --- /dev/null +++ b/crates/atuin-ai/src/tui/components/session_continue.rs @@ -0,0 +1,49 @@ +use chrono_humanize::HumanTime; +use eye_declare::{Elements, Hooks, Span, Text, component, element, props}; +use ratatui::style::{Color, Modifier, Style}; + +#[props] +pub(crate) struct SessionContinue { + pub continued_at: Option<chrono::DateTime<chrono::Utc>>, +} + +#[derive(Default)] +pub(crate) struct SessionContinueState { + /// Frozen on mount so the label doesn't change on every render. + label: Option<String>, +} + +#[component(props = SessionContinue, state = SessionContinueState)] +fn session_continue( + _props: &SessionContinue, + state: &SessionContinueState, + hooks: &mut Hooks<SessionContinue, SessionContinueState>, +) -> Elements { + hooks.use_mount(|props, state| { + state.label = Some(match props.continued_at { + Some(t) => { + let human = HumanTime::from(t - chrono::Utc::now()); + format!( + " Continuing previous session (last active {human}) - type /new to start a new session" + ) + } + None => { + " Continuing previous session - type /new to start a new session".to_string() + } + }); + }); + + let resume_label = state + .label + .as_deref() + .unwrap_or(" Continuing previous session - type /new to start a new session"); + + element! { + Text { + Span( + text: resume_label, + style: Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC), + ) + } + } +} |
