Skip to content

Commit 2e5e367

Browse files
Merge pull request #53 from gitcoder89431/polish-footer-01
Polish footer 01
2 parents 9d9ff05 + 5c4fc65 commit 2e5e367

File tree

4 files changed

+210
-51
lines changed

4 files changed

+210
-51
lines changed

.idea/workspace.xml

Lines changed: 34 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/agentic-tui/src/ui/app.rs

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{
2-
chat::render_chat,
2+
chat::{render_chat, AutocompleteParams},
33
footer::render_footer,
44
header::render_header,
55
model_selection_modal::{render_model_selection_modal, ModelSelectionParams},
@@ -127,6 +127,8 @@ pub struct App {
127127
coaching_tip: (String, String),
128128
local_tokens_used: u32, // Token count for current local request
129129
cloud_tokens_used: u32, // Token count for current cloud request
130+
show_autocomplete: bool,
131+
autocomplete_index: usize,
130132
}
131133

132134
impl App {
@@ -158,6 +160,8 @@ impl App {
158160
coaching_tip: (String::new(), String::new()),
159161
local_tokens_used: 0,
160162
cloud_tokens_used: 0,
163+
show_autocomplete: false,
164+
autocomplete_index: 0,
161165
}
162166
}
163167

@@ -510,6 +514,11 @@ impl App {
510514
self.mode,
511515
&self.edit_buffer,
512516
self.agent_status,
517+
AutocompleteParams {
518+
show: self.show_autocomplete,
519+
commands: &self.get_filtered_slash_commands(),
520+
selected_index: self.autocomplete_index,
521+
},
513522
);
514523
}
515524
})?;
@@ -711,11 +720,18 @@ impl App {
711720
self.attempt_start();
712721
}
713722
}
714-
// TODO: Handle 'a' for About mode
723+
KeyCode::Char('a') => {
724+
// Show About modal - same as /about command
725+
self.coaching_tip = (
726+
"About RuixenOS v0.1.0".to_string(),
727+
"🎯 The Curiosity Machine\nTransforming queries into thoughtful Ruixen inquiries since 2025.\nBuilt with Rust, ratatui, and endless wonder.".to_string(),
728+
);
729+
self.mode = AppMode::CoachingTip;
730+
}
715731
_ => {}
716732
},
717733
AppMode::Settings => match key.code {
718-
KeyCode::Char('r') => self.mode = AppMode::Normal,
734+
KeyCode::Esc => self.mode = AppMode::Normal,
719735
KeyCode::Char('s') => {
720736
if let Err(e) = self.settings.save() {
721737
eprintln!("Warning: Failed to save settings: {}", e);
@@ -863,15 +879,49 @@ impl App {
863879
// Return to Normal mode
864880
self.mode = AppMode::Normal;
865881
self.edit_buffer.clear();
882+
self.show_autocomplete = false;
866883
}
867884
KeyCode::Enter => {
868-
// Process chat message
869-
if !self.edit_buffer.is_empty() {
885+
if self.show_autocomplete
886+
&& !self.get_filtered_slash_commands().is_empty()
887+
{
888+
// Apply selected autocomplete suggestion
889+
let filtered = self.get_filtered_slash_commands();
890+
let selected_command = &filtered[self.autocomplete_index].0;
891+
self.edit_buffer = selected_command.clone();
892+
self.show_autocomplete = false;
893+
} else if !self.edit_buffer.is_empty() {
870894
self.handle_chat_message();
895+
self.show_autocomplete = false;
896+
}
897+
}
898+
KeyCode::Tab => {
899+
if self.show_autocomplete
900+
&& !self.get_filtered_slash_commands().is_empty()
901+
{
902+
// Apply selected autocomplete suggestion
903+
let filtered = self.get_filtered_slash_commands();
904+
let selected_command = &filtered[self.autocomplete_index].0;
905+
self.edit_buffer = selected_command.clone();
906+
self.show_autocomplete = false;
907+
}
908+
}
909+
KeyCode::Up if self.show_autocomplete => {
910+
if self.autocomplete_index > 0 {
911+
self.autocomplete_index -= 1;
912+
}
913+
}
914+
KeyCode::Down if self.show_autocomplete => {
915+
let filtered_commands = self.get_filtered_slash_commands();
916+
if self.autocomplete_index
917+
< filtered_commands.len().saturating_sub(1)
918+
{
919+
self.autocomplete_index += 1;
871920
}
872921
}
873922
KeyCode::Backspace => {
874923
self.edit_buffer.pop();
924+
self.update_autocomplete();
875925
}
876926
KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
877927
// Ctrl+V: Allow pasting in chat
@@ -881,6 +931,7 @@ impl App {
881931
}
882932
KeyCode::Char(c) => {
883933
self.edit_buffer.push(c);
934+
self.update_autocomplete();
884935
}
885936
_ => {}
886937
},
@@ -1000,8 +1051,13 @@ impl App {
10001051
},
10011052
AppMode::CoachingTip => match key.code {
10021053
KeyCode::Enter | KeyCode::Esc => {
1003-
// Return to chat mode to try again
1004-
self.mode = AppMode::Chat;
1054+
// About modal should return to main menu, errors return to chat
1055+
if self.coaching_tip.0.contains("About RuixenOS") {
1056+
self.mode = AppMode::Normal;
1057+
} else {
1058+
// Error messages return to chat to try again
1059+
self.mode = AppMode::Chat;
1060+
}
10051061
}
10061062
_ => {}
10071063
},
@@ -1152,20 +1208,54 @@ impl App {
11521208
"/quit" | "/exit" => {
11531209
self.should_quit = true;
11541210
}
1155-
"/theme" => {
1156-
self.theme.toggle();
1157-
self.settings.theme = self.theme.variant();
1158-
if let Err(e) = self.settings.save() {
1159-
eprintln!("Warning: Failed to save settings: {}", e);
1160-
}
1161-
}
11621211
_ => {
11631212
// Unknown command - could show help message or ignore
1164-
println!("Unknown command: {}", command);
1213+
self.coaching_tip = (
1214+
"Unknown Command".to_string(),
1215+
format!(
1216+
"Command '{}' not recognized. Try /settings or /quit",
1217+
command
1218+
),
1219+
);
1220+
self.mode = AppMode::CoachingTip;
11651221
}
11661222
}
11671223
}
11681224

1225+
fn update_autocomplete(&mut self) {
1226+
if self.edit_buffer.starts_with('/') {
1227+
let filtered = self.get_filtered_slash_commands();
1228+
self.show_autocomplete = !filtered.is_empty();
1229+
self.autocomplete_index = 0; // Reset selection to top
1230+
} else {
1231+
self.show_autocomplete = false;
1232+
}
1233+
}
1234+
1235+
fn get_filtered_slash_commands(&self) -> Vec<(String, String)> {
1236+
// Only 2 slash commands - About is main menu only
1237+
let available_commands = vec![
1238+
(
1239+
"/settings".to_string(),
1240+
"Configure app settings".to_string(),
1241+
),
1242+
("/quit".to_string(), "Exit the application".to_string()),
1243+
];
1244+
1245+
if self.edit_buffer == "/" {
1246+
// Show all commands when just "/" is typed
1247+
available_commands
1248+
} else if self.edit_buffer.starts_with('/') {
1249+
// Filter commands based on what's typed
1250+
available_commands
1251+
.into_iter()
1252+
.filter(|(cmd, _)| cmd.starts_with(&self.edit_buffer))
1253+
.collect()
1254+
} else {
1255+
vec![]
1256+
}
1257+
}
1258+
11691259
fn start_editing_current_selection(&mut self) {
11701260
match self.settings_selection {
11711261
SettingsSelection::Endpoint => {

crates/agentic-tui/src/ui/chat.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ use crate::ui::app::{AgentStatus, AppMode};
22
use agentic_core::theme::{Element, Theme};
33
use ratatui::{
44
prelude::{Alignment, Constraint, Direction, Frame, Layout, Rect},
5-
widgets::{Block, Borders, Paragraph},
5+
text::{Line, Span},
6+
widgets::{Block, Borders, List, ListItem, Paragraph},
67
};
78

9+
pub struct AutocompleteParams<'a> {
10+
pub show: bool,
11+
pub commands: &'a [(String, String)],
12+
pub selected_index: usize,
13+
}
14+
815
const MAIN_LOGO: &str = r#"
916
╔═══════════════════════════════════════════════════════════════╗
1017
║ ║
@@ -63,6 +70,7 @@ pub fn render_chat(
6370
mode: AppMode,
6471
chat_input: &str,
6572
agent_status: AgentStatus,
73+
autocomplete: AutocompleteParams,
6674
) {
6775
let chat_block = Block::new()
6876
.borders(Borders::ALL)
@@ -95,6 +103,28 @@ pub fn render_chat(
95103
AppMode::Chat => {
96104
// Clean canvas when user is typing - completely empty
97105
// The spiral galaxy has disappeared, leaving pure focus space
106+
107+
// Show autocomplete dropdown if needed
108+
if autocomplete.show && !autocomplete.commands.is_empty() {
109+
let dropdown_height = (autocomplete.commands.len() as u16).clamp(1, 6) + 2; // Add 2 for borders
110+
let dropdown_width = 50u16.min(inner_area.width - 4); // Make wider and ensure space for borders
111+
112+
// Position dropdown just above the footer bar (bottom of chat area)
113+
let dropdown_area = Rect::new(
114+
inner_area.x + 2,
115+
inner_area.y + inner_area.height.saturating_sub(dropdown_height + 1),
116+
dropdown_width,
117+
dropdown_height,
118+
);
119+
120+
render_autocomplete_dropdown(
121+
frame,
122+
dropdown_area,
123+
theme,
124+
autocomplete.commands,
125+
autocomplete.selected_index,
126+
);
127+
}
98128
}
99129
_ => {
100130
// Normal mode - show main logo with status-based message
@@ -161,3 +191,42 @@ pub fn render_chat(
161191
}
162192
}
163193
}
194+
195+
fn render_autocomplete_dropdown(
196+
frame: &mut Frame,
197+
area: Rect,
198+
theme: &Theme,
199+
commands: &[(String, String)],
200+
selected_index: usize,
201+
) {
202+
let items: Vec<ListItem> = commands
203+
.iter()
204+
.enumerate()
205+
.map(|(i, (cmd, desc))| {
206+
let (cmd_style, desc_style) = if i == selected_index {
207+
// Selected item: use accent color for command, normal for description
208+
(theme.ratatui_style(Element::Accent), theme.text_style())
209+
} else {
210+
// Non-selected: normal text for both
211+
(theme.text_style(), theme.ratatui_style(Element::Inactive))
212+
};
213+
214+
let line = Line::from(vec![
215+
Span::styled(cmd.clone(), cmd_style),
216+
Span::raw(" - "),
217+
Span::styled(desc.clone(), desc_style),
218+
]);
219+
220+
ListItem::new(line)
221+
})
222+
.collect();
223+
224+
let list = List::new(items).block(
225+
Block::default()
226+
.borders(Borders::ALL)
227+
.title("Commands")
228+
.style(theme.ratatui_style(Element::Active)),
229+
);
230+
231+
frame.render_widget(list, area);
232+
}

crates/agentic-tui/src/ui/settings_modal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub fn render_settings_modal(
141141
let action_text = match mode {
142142
AppMode::EditingApiKey => "[ENTER] Save | [CTRL+V] Paste | [ESC] Cancel",
143143
AppMode::EditingEndpoint => "[ENTER] Save | [ESC] Cancel",
144-
_ => "[ENTER] Edit | [↑↓] Navigate | [S]ave | [R]eturn",
144+
_ => "[↑↓] Navigate | [S]ave changes | [ESC] Return",
145145
};
146146
let action_style = if selection == SettingsSelection::Save {
147147
theme.highlight_style()

0 commit comments

Comments
 (0)