feat(command-palette): add grouped suggestions, history tracking, and model/provider fuzzy matching
- Export `PaletteGroup` and `PaletteSuggestion` to represent suggestion metadata. - Implement command history with deduplication, capacity limit, and recent‑command suggestions. - Enhance dynamic suggestion logic to include history, commands, models, and providers with fuzzy ranking. - Add UI rendering for grouped suggestions, header with command palette label, and footer instructions. - Update help text with new shortcuts (Ctrl+P, layout save/load) and expose new agent/layout commands.
This commit is contained in:
@@ -28,8 +28,8 @@ use crate::events::Event;
|
||||
use crate::model_info_panel::ModelInfoPanel;
|
||||
use crate::state::{
|
||||
CodeWorkspace, CommandPalette, FileFilterMode, FileNode, FileTreeState, ModelPaletteEntry,
|
||||
PaneDirection, PaneRestoreRequest, RepoSearchMessage, RepoSearchState, SplitAxis,
|
||||
SymbolSearchMessage, SymbolSearchState, WorkspaceSnapshot, spawn_repo_search_task,
|
||||
PaletteSuggestion, PaneDirection, PaneRestoreRequest, RepoSearchMessage, RepoSearchState,
|
||||
SplitAxis, SymbolSearchMessage, SymbolSearchState, WorkspaceSnapshot, spawn_repo_search_task,
|
||||
spawn_symbol_search_task,
|
||||
};
|
||||
use crate::ui::format_tool_output;
|
||||
@@ -164,6 +164,7 @@ pub const HELP_TAB_COUNT: usize = 7;
|
||||
pub struct ChatApp {
|
||||
controller: SessionController,
|
||||
pub mode: InputMode,
|
||||
mode_flash_until: Option<Instant>,
|
||||
pub status: String,
|
||||
pub error: Option<String>,
|
||||
models: Vec<ModelInfo>, // All models fetched
|
||||
@@ -383,6 +384,7 @@ impl ChatApp {
|
||||
let mut app = Self {
|
||||
controller,
|
||||
mode: InputMode::Normal,
|
||||
mode_flash_until: None,
|
||||
status: if show_onboarding {
|
||||
ONBOARDING_STATUS_LINE.to_string()
|
||||
} else {
|
||||
@@ -743,7 +745,7 @@ impl ChatApp {
|
||||
self.file_tree_mut().reveal(&absolute);
|
||||
self.focused_panel = FocusedPanel::Code;
|
||||
self.ensure_focus_valid();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.status = format!("Opened {}:{}:{column}", display, line_number);
|
||||
self.error = None;
|
||||
}
|
||||
@@ -796,7 +798,7 @@ impl ChatApp {
|
||||
}
|
||||
self.focused_panel = FocusedPanel::Code;
|
||||
self.ensure_focus_valid();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.status = format!("Opened scratch buffer for {title}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -917,7 +919,7 @@ impl ChatApp {
|
||||
self.file_tree_mut().reveal(&entry.file);
|
||||
self.focused_panel = FocusedPanel::Code;
|
||||
self.ensure_focus_valid();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.status = format!(
|
||||
"Jumped to {} {}:{}",
|
||||
entry.kind.label(),
|
||||
@@ -1269,10 +1271,23 @@ impl ChatApp {
|
||||
self.command_palette.buffer()
|
||||
}
|
||||
|
||||
pub fn command_suggestions(&self) -> &[String] {
|
||||
pub fn command_suggestions(&self) -> &[PaletteSuggestion] {
|
||||
self.command_palette.suggestions()
|
||||
}
|
||||
|
||||
fn set_input_mode(&mut self, mode: InputMode) {
|
||||
if self.mode != mode {
|
||||
self.mode_flash_until = Some(Instant::now() + Duration::from_millis(240));
|
||||
}
|
||||
self.mode = mode;
|
||||
}
|
||||
|
||||
pub fn mode_flash_active(&self) -> bool {
|
||||
self.mode_flash_until
|
||||
.map(|deadline| Instant::now() < deadline)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn selected_suggestion(&self) -> usize {
|
||||
self.command_palette.selected_index()
|
||||
}
|
||||
@@ -3189,13 +3204,13 @@ impl ChatApp {
|
||||
}
|
||||
|
||||
if is_question_mark && matches!(self.mode, InputMode::Normal) {
|
||||
self.mode = InputMode::Help;
|
||||
self.set_input_mode(InputMode::Help);
|
||||
self.status = "Help".to_string();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
|
||||
if is_repo_search && matches!(self.mode, InputMode::Normal) {
|
||||
self.mode = InputMode::RepoSearch;
|
||||
self.set_input_mode(InputMode::RepoSearch);
|
||||
if self.repo_search.query_input().is_empty() {
|
||||
*self.repo_search.status_mut() =
|
||||
Some("Type a pattern · Enter runs ripgrep".to_string());
|
||||
@@ -3205,7 +3220,7 @@ impl ChatApp {
|
||||
}
|
||||
|
||||
if is_symbol_search_key && matches!(self.mode, InputMode::Normal) {
|
||||
self.mode = InputMode::SymbolSearch;
|
||||
self.set_input_mode(InputMode::SymbolSearch);
|
||||
self.symbol_search.clear_query();
|
||||
self.status = "Symbol search active".to_string();
|
||||
self.start_symbol_search().await?;
|
||||
@@ -3358,7 +3373,7 @@ impl ChatApp {
|
||||
.to_string();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
self.mode = InputMode::Visual;
|
||||
self.set_input_mode(InputMode::Visual);
|
||||
|
||||
match self.focused_panel {
|
||||
FocusedPanel::Input => {
|
||||
@@ -3391,45 +3406,54 @@ impl ChatApp {
|
||||
"-- VISUAL -- (move with j/k, yank with y)".to_string();
|
||||
}
|
||||
(KeyCode::Char(':'), KeyModifiers::NONE) => {
|
||||
self.mode = InputMode::Command;
|
||||
self.set_input_mode(InputMode::Command);
|
||||
self.command_palette.clear();
|
||||
self.command_palette.ensure_suggestions();
|
||||
self.status = ":".to_string();
|
||||
}
|
||||
(KeyCode::Char('p'), modifiers)
|
||||
if modifiers.contains(KeyModifiers::CONTROL) =>
|
||||
{
|
||||
self.set_input_mode(InputMode::Command);
|
||||
self.command_palette.clear();
|
||||
self.command_palette.ensure_suggestions();
|
||||
self.status = ":".to_string();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
// Enter editing mode
|
||||
(KeyCode::Enter, KeyModifiers::NONE)
|
||||
| (KeyCode::Char('i'), KeyModifiers::NONE) => {
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
}
|
||||
(KeyCode::Char('a'), KeyModifiers::NONE) => {
|
||||
// Append - move right and enter insert mode
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
self.textarea.move_cursor(tui_textarea::CursorMove::Forward);
|
||||
}
|
||||
(KeyCode::Char('A'), KeyModifiers::SHIFT) => {
|
||||
// Append at end of line
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
self.textarea.move_cursor(tui_textarea::CursorMove::End);
|
||||
}
|
||||
(KeyCode::Char('I'), KeyModifiers::SHIFT) => {
|
||||
// Insert at start of line
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
self.textarea.move_cursor(tui_textarea::CursorMove::Head);
|
||||
}
|
||||
(KeyCode::Char('o'), KeyModifiers::NONE) => {
|
||||
// Insert newline below and enter edit mode
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
self.textarea.move_cursor(tui_textarea::CursorMove::End);
|
||||
self.textarea.insert_newline();
|
||||
}
|
||||
(KeyCode::Char('O'), KeyModifiers::NONE) => {
|
||||
// Insert newline above and enter edit mode
|
||||
self.mode = InputMode::Editing;
|
||||
self.set_input_mode(InputMode::Editing);
|
||||
self.sync_buffer_to_textarea();
|
||||
self.textarea.move_cursor(tui_textarea::CursorMove::Head);
|
||||
self.textarea.insert_newline();
|
||||
@@ -3773,7 +3797,7 @@ impl ChatApp {
|
||||
}
|
||||
(KeyCode::Esc, KeyModifiers::NONE) => {
|
||||
self.pending_key = None;
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
_ => {
|
||||
self.pending_key = None;
|
||||
@@ -3782,7 +3806,7 @@ impl ChatApp {
|
||||
}
|
||||
InputMode::RepoSearch => match (key.code, key.modifiers) {
|
||||
(KeyCode::Esc, _) => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.status = "Normal mode".to_string();
|
||||
}
|
||||
(KeyCode::Enter, modifiers) if modifiers.contains(KeyModifiers::ALT) => {
|
||||
@@ -3828,9 +3852,7 @@ impl ChatApp {
|
||||
Some("Press Enter to search".to_string());
|
||||
self.status = format!("Query: {}", self.repo_search.query_input());
|
||||
}
|
||||
(KeyCode::Up, _)
|
||||
| (KeyCode::Char('k'), KeyModifiers::NONE)
|
||||
| (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
|
||||
(KeyCode::Up, _) | (KeyCode::Char('k'), KeyModifiers::NONE) => {
|
||||
self.repo_search.move_selection(-1);
|
||||
}
|
||||
(KeyCode::Down, _)
|
||||
@@ -3855,7 +3877,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::SymbolSearch => match (key.code, key.modifiers) {
|
||||
(KeyCode::Esc, _) => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.status = "Normal mode".to_string();
|
||||
}
|
||||
(KeyCode::Enter, _) => {
|
||||
@@ -3890,9 +3912,7 @@ impl ChatApp {
|
||||
self.symbol_search.push_query_char(c);
|
||||
self.status = format!("Symbol filter: {}", self.symbol_search.query());
|
||||
}
|
||||
(KeyCode::Up, _)
|
||||
| (KeyCode::Char('k'), KeyModifiers::NONE)
|
||||
| (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
|
||||
(KeyCode::Up, _) | (KeyCode::Char('k'), KeyModifiers::NONE) => {
|
||||
self.symbol_search.move_selection(-1);
|
||||
}
|
||||
(KeyCode::Down, _)
|
||||
@@ -3909,25 +3929,34 @@ impl ChatApp {
|
||||
_ => {}
|
||||
},
|
||||
InputMode::Editing => match (key.code, key.modifiers) {
|
||||
(KeyCode::Char('p'), modifiers)
|
||||
if modifiers.contains(KeyModifiers::CONTROL) =>
|
||||
{
|
||||
self.sync_textarea_to_buffer();
|
||||
self.set_input_mode(InputMode::Command);
|
||||
self.command_palette.clear();
|
||||
self.command_palette.ensure_suggestions();
|
||||
self.status = ":".to_string();
|
||||
}
|
||||
(KeyCode::Char('c'), modifiers)
|
||||
if modifiers.contains(KeyModifiers::CONTROL) =>
|
||||
{
|
||||
let _ = self.cancel_active_generation()?;
|
||||
self.sync_textarea_to_buffer();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.reset_status();
|
||||
}
|
||||
(KeyCode::Esc, KeyModifiers::NONE) => {
|
||||
// Sync textarea content to input buffer before leaving edit mode
|
||||
self.sync_textarea_to_buffer();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.reset_status();
|
||||
}
|
||||
(KeyCode::Char('['), modifiers)
|
||||
if modifiers.contains(KeyModifiers::CONTROL) =>
|
||||
{
|
||||
self.sync_textarea_to_buffer();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.reset_status();
|
||||
}
|
||||
(KeyCode::Char('j' | 'J'), m) if m.contains(KeyModifiers::CONTROL) => {
|
||||
@@ -3940,7 +3969,7 @@ impl ChatApp {
|
||||
// Clear the textarea by setting it to empty
|
||||
self.textarea = TextArea::default();
|
||||
configure_textarea_defaults(&mut self.textarea);
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
(KeyCode::Enter, _) => {
|
||||
// Any Enter with modifiers keeps editing and inserts a newline via tui-textarea
|
||||
@@ -3986,7 +4015,7 @@ impl ChatApp {
|
||||
if matches!(self.focused_panel, FocusedPanel::Input) {
|
||||
self.textarea.cancel_selection();
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.visual_start = None;
|
||||
self.visual_end = None;
|
||||
self.reset_status();
|
||||
@@ -4028,7 +4057,7 @@ impl ChatApp {
|
||||
FocusedPanel::Files => {}
|
||||
FocusedPanel::Code => {}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.visual_start = None;
|
||||
self.visual_end = None;
|
||||
}
|
||||
@@ -4062,7 +4091,7 @@ impl ChatApp {
|
||||
FocusedPanel::Files => {}
|
||||
FocusedPanel::Code => {}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.visual_start = None;
|
||||
self.visual_end = None;
|
||||
}
|
||||
@@ -4221,7 +4250,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::Command => match (key.code, key.modifiers) {
|
||||
(KeyCode::Esc, _) => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.command_palette.clear();
|
||||
self.reset_status();
|
||||
}
|
||||
@@ -4244,6 +4273,10 @@ impl ChatApp {
|
||||
let command = parts.first().copied().unwrap_or("");
|
||||
let args = &parts[1..];
|
||||
|
||||
if !cmd_owned.is_empty() {
|
||||
self.command_palette.remember(&cmd_owned);
|
||||
}
|
||||
|
||||
match command {
|
||||
"q" | "quit" => {
|
||||
return Ok(AppState::Quit);
|
||||
@@ -4302,7 +4335,7 @@ impl ChatApp {
|
||||
Ok(sessions) => {
|
||||
self.saved_sessions = sessions;
|
||||
self.selected_session_index = 0;
|
||||
self.mode = InputMode::SessionBrowser;
|
||||
self.set_input_mode(InputMode::SessionBrowser);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4367,7 +4400,7 @@ impl ChatApp {
|
||||
Ok(sessions) => {
|
||||
self.saved_sessions = sessions;
|
||||
self.selected_session_index = 0;
|
||||
self.mode = InputMode::SessionBrowser;
|
||||
self.set_input_mode(InputMode::SessionBrowser);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4434,7 +4467,7 @@ impl ChatApp {
|
||||
}
|
||||
}
|
||||
"h" | "help" => {
|
||||
self.mode = InputMode::Help;
|
||||
self.set_input_mode(InputMode::Help);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4496,7 +4529,7 @@ impl ChatApp {
|
||||
Ok(_) => self.error = None,
|
||||
Err(err) => self.error = Some(err.to_string()),
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4509,7 +4542,7 @@ impl ChatApp {
|
||||
self.error = Some(err.to_string());
|
||||
}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4581,7 +4614,7 @@ impl ChatApp {
|
||||
format!("No provider matching '{}'", filter.trim());
|
||||
}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
@@ -4603,25 +4636,79 @@ impl ChatApp {
|
||||
Err(err) => self.error = Some(err.to_string()),
|
||||
}
|
||||
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
// "run-agent" command removed to break circular dependency on owlen-cli.
|
||||
"agent" => {
|
||||
if self.agent_running {
|
||||
if let Some(subcommand) = args.first() {
|
||||
match subcommand.to_lowercase().as_str() {
|
||||
"status" => {
|
||||
let armed =
|
||||
if self.agent_mode { "armed" } else { "idle" };
|
||||
let running = if self.agent_running {
|
||||
"running"
|
||||
} else {
|
||||
"stopped"
|
||||
};
|
||||
self.status =
|
||||
format!("Agent status: {armed} · {running}");
|
||||
self.error = None;
|
||||
}
|
||||
"start" | "arm" => {
|
||||
if self.agent_running {
|
||||
self.status =
|
||||
"Agent is already running".to_string();
|
||||
} else {
|
||||
self.agent_mode = true;
|
||||
self.status = "Agent armed. Next message will be processed by the agent.".to_string();
|
||||
self.error = None;
|
||||
}
|
||||
}
|
||||
"stop" => {
|
||||
if self.agent_running {
|
||||
self.agent_running = false;
|
||||
self.agent_mode = false;
|
||||
self.agent_actions = None;
|
||||
self.status =
|
||||
"Agent execution stopped".to_string();
|
||||
self.error = None;
|
||||
} else if self.agent_mode {
|
||||
self.agent_mode = false;
|
||||
self.agent_actions = None;
|
||||
self.status = "Agent disarmed".to_string();
|
||||
self.error = None;
|
||||
} else {
|
||||
self.status =
|
||||
"No agent is currently running".to_string();
|
||||
}
|
||||
}
|
||||
other => {
|
||||
self.error =
|
||||
Some(format!("Unknown agent command: {other}"));
|
||||
}
|
||||
}
|
||||
} else if self.agent_running {
|
||||
self.status = "Agent is already running".to_string();
|
||||
} else {
|
||||
self.agent_mode = true;
|
||||
self.status = "Agent mode enabled. Next message will be processed by agent.".to_string();
|
||||
self.error = None;
|
||||
}
|
||||
}
|
||||
"stop-agent" => {
|
||||
if self.agent_running {
|
||||
self.agent_running = false;
|
||||
self.agent_mode = false;
|
||||
self.status = "Agent execution stopped".to_string();
|
||||
self.agent_actions = None;
|
||||
self.status = "Agent execution stopped".to_string();
|
||||
self.error = None;
|
||||
} else if self.agent_mode {
|
||||
self.agent_mode = false;
|
||||
self.agent_actions = None;
|
||||
self.status = "Agent disarmed".to_string();
|
||||
self.error = None;
|
||||
} else {
|
||||
self.status = "No agent is currently running".to_string();
|
||||
}
|
||||
@@ -4708,10 +4795,47 @@ impl ChatApp {
|
||||
.position(|name| name == current_theme)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.mode = InputMode::ThemeBrowser;
|
||||
self.set_input_mode(InputMode::ThemeBrowser);
|
||||
self.command_palette.clear();
|
||||
return Ok(AppState::Running);
|
||||
}
|
||||
"layout" => {
|
||||
if let Some(subcommand) = args.first() {
|
||||
match subcommand.to_lowercase().as_str() {
|
||||
"save" => {
|
||||
if self.code_workspace.tabs().is_empty() {
|
||||
self.status =
|
||||
"No open panes to save".to_string();
|
||||
} else {
|
||||
self.persist_workspace_layout();
|
||||
self.status =
|
||||
"Workspace layout saved".to_string();
|
||||
self.error = None;
|
||||
}
|
||||
}
|
||||
"load" => match self.restore_workspace_layout().await {
|
||||
Ok(()) => {
|
||||
self.status =
|
||||
"Workspace layout restored".to_string();
|
||||
self.error = None;
|
||||
}
|
||||
Err(err) => {
|
||||
self.error = Some(err.to_string());
|
||||
self.status =
|
||||
"Failed to restore workspace layout"
|
||||
.to_string();
|
||||
}
|
||||
},
|
||||
other => {
|
||||
self.error = Some(format!(
|
||||
"Unknown layout command: {other}"
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.status = "Usage: :layout <save|load>".to_string();
|
||||
}
|
||||
}
|
||||
"reload" => {
|
||||
// Reload config
|
||||
match owlen_core::config::Config::load(None) {
|
||||
@@ -4809,7 +4933,7 @@ impl ChatApp {
|
||||
}
|
||||
}
|
||||
self.command_palette.clear();
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
(KeyCode::Char(c), KeyModifiers::NONE)
|
||||
| (KeyCode::Char(c), KeyModifiers::SHIFT) => {
|
||||
@@ -4824,7 +4948,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::ProviderSelection => match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if let Some(provider) =
|
||||
@@ -4833,7 +4957,7 @@ impl ChatApp {
|
||||
self.selected_provider = provider.clone();
|
||||
// Update model selection based on new provider (await async)
|
||||
self.sync_selected_model_index().await; // Update model selection based on new provider
|
||||
self.mode = InputMode::ModelSelection;
|
||||
self.set_input_mode(InputMode::ModelSelection);
|
||||
}
|
||||
}
|
||||
KeyCode::Up => {
|
||||
@@ -4854,7 +4978,7 @@ impl ChatApp {
|
||||
self.set_model_info_visible(false);
|
||||
self.status = "Closed model info panel".to_string();
|
||||
} else {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
@@ -4901,7 +5025,7 @@ impl ChatApp {
|
||||
self.set_model_info_visible(false);
|
||||
self.status = "Closed model info panel".to_string();
|
||||
} else {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
}
|
||||
KeyCode::Char('i') => {
|
||||
@@ -5017,7 +5141,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::Help => match key.code {
|
||||
KeyCode::Esc | KeyCode::Enter | KeyCode::Char('q') => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.help_tab_index = 0; // Reset to first tab
|
||||
}
|
||||
KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => {
|
||||
@@ -5044,7 +5168,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::SessionBrowser => match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Load selected session
|
||||
@@ -5067,7 +5191,7 @@ impl ChatApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
if self.selected_session_index > 0 {
|
||||
@@ -5106,7 +5230,7 @@ impl ChatApp {
|
||||
},
|
||||
InputMode::ThemeBrowser => match key.code {
|
||||
KeyCode::Esc | KeyCode::Char('q') => {
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// Apply selected theme
|
||||
@@ -5124,7 +5248,7 @@ impl ChatApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
}
|
||||
KeyCode::Up | KeyCode::Char('k') => {
|
||||
if self.selected_theme_index > 0 {
|
||||
@@ -5965,7 +6089,7 @@ impl ChatApp {
|
||||
self.error = Some(format!("Failed to save config: {}", err));
|
||||
}
|
||||
}
|
||||
self.mode = InputMode::Normal;
|
||||
self.set_input_mode(InputMode::Normal);
|
||||
self.set_model_info_visible(false);
|
||||
Ok(())
|
||||
}
|
||||
@@ -5978,10 +6102,10 @@ impl ChatApp {
|
||||
}
|
||||
|
||||
if self.available_providers.len() <= 1 {
|
||||
self.mode = InputMode::ModelSelection;
|
||||
self.set_input_mode(InputMode::ModelSelection);
|
||||
self.ensure_valid_model_selection();
|
||||
} else {
|
||||
self.mode = InputMode::ProviderSelection;
|
||||
self.set_input_mode(InputMode::ProviderSelection);
|
||||
}
|
||||
self.status = "Select a model to use".to_string();
|
||||
Ok(())
|
||||
@@ -6257,7 +6381,7 @@ impl ChatApp {
|
||||
);
|
||||
self.status = "Model unavailable".to_string();
|
||||
let _ = self.refresh_models().await;
|
||||
self.mode = InputMode::ProviderSelection;
|
||||
self.set_input_mode(InputMode::ProviderSelection);
|
||||
} else {
|
||||
self.error = Some(message);
|
||||
self.status = "Request failed".to_string();
|
||||
|
||||
Reference in New Issue
Block a user