use crossterm::event::KeyCode;
use crate::tui::app::{App, GlobalMode, LoadingPhase, ModelsMode};
pub async fn handle_models_key(app: &mut App, key: crossterm::event::KeyEvent) {
if app.search.filtering_local {
match key.code {
KeyCode::Esc => {
app.search.filtering_local = false;
app.search.local_filter.clear();
app.on_model_selection_change();
}
KeyCode::Enter => {
app.search.filtering_local = false;
}
KeyCode::Char(c) => {
app.search.local_filter.push(c);
app.on_model_selection_change();
}
KeyCode::Backspace => {
app.search.local_filter.pop();
app.on_model_selection_change();
}
_ => {}
}
return;
}
match key.code {
KeyCode::Char('f') => {
if matches!(app.models_mode, ModelsMode::List) {
app.search.filtering_local = true;
if app.selected_model_idx.is_none() {
let filtered = app.get_filtered_model_indices();
if !filtered.is_empty() {
app.selected_model_idx = Some(filtered[0]);
app.on_model_selection_change();
}
}
}
}
KeyCode::Up | KeyCode::Char('k') => {
let filtered = app.get_filtered_model_indices();
if let Some(idx) = app.selected_model_idx {
if let Some(pos) = filtered.iter().position(|&i| i == idx) {
if pos > 0 {
app.selected_model_idx = Some(filtered[pos - 1]);
app.on_model_selection_change();
}
} else if !filtered.is_empty() {
app.selected_model_idx = Some(filtered[0]);
app.on_model_selection_change();
}
} else if !filtered.is_empty() {
app.selected_model_idx = Some(filtered[0]);
app.on_model_selection_change();
}
}
KeyCode::Down | KeyCode::Char('j') => {
let filtered = app.get_filtered_model_indices();
if let Some(idx) = app.selected_model_idx {
if let Some(pos) = filtered.iter().position(|&i| i == idx) {
if pos + 1 < filtered.len() {
app.selected_model_idx = Some(filtered[pos + 1]);
app.on_model_selection_change();
}
} else if !filtered.is_empty() {
app.selected_model_idx = Some(filtered[0]);
app.on_model_selection_change();
}
} else if !filtered.is_empty() {
app.selected_model_idx = Some(filtered[0]);
app.on_model_selection_change();
}
}
KeyCode::Enter | KeyCode::Char('l') => {
if app.pending.backend_resolving {
app.add_log(
"Wait for backend installation to finish...",
crate::config::LogLevel::Info,
);
return;
}
if let Some(idx) = app.selected_model_idx {
let model = app.models[idx].clone();
let already_loaded = matches!(
app.model_states.get(&model.display_name),
Some(crate::models::ModelState::Loaded { .. })
);
if already_loaded {
app.add_log(
format!("{} is already loaded", model.display_name),
crate::config::LogLevel::Info,
);
} else {
app.update_model_metadata();
let settings = app.selected_model_settings();
if let Some(handle) = &app.server.server_handle
&& !crate::backend::server::check_health(&handle.host, handle.port).await
{
app.add_log(
"Router unresponsive, restarting...",
crate::config::LogLevel::Info,
);
if let Some(h) = app.server.server_handle.take() {
app.pending.pending_kill = Some(h);
}
}
if app.server.server_handle.is_none() {
app.ui.last_error_message = None;
if app.server_mode == crate::models::ServerMode::BenchTune {
let bench_tune_config = crate::models::BenchTuneConfig::new(
model.path.clone(),
3, crate::models::BENCHMARK_PROMPT.to_string(),
);
app.ui.global_mode = crate::tui::app::GlobalMode::BenchTuneSetup {
config: bench_tune_config,
selected_idx: 0,
editing_param: false,
editing_param_field: 0,
param_edit_buffer: String::new(),
param_edit_cursor_pos: 0,
bench_mode_selection: 0,
editing_prompt: false,
editing_kwargs: false,
};
return;
}
if app.server_mode == crate::models::ServerMode::Router {
app.pending.pending_spawn = Some((None, settings.clone()));
app.pending.pending_api_load = Some((
model.display_name.clone(),
Some(model.path.to_string_lossy().to_string()),
));
app.loading.loading_phases =
std::iter::once(LoadingPhase::ServerStarting).collect();
app.loading.last_active_phase = Some(LoadingPhase::ServerStarting);
app.loading.loading_progress = 0.25;
app.add_log(
"Starting router server...".to_string(),
crate::config::LogLevel::Info,
);
} else {
app.pending.pending_spawn = Some((Some(model.clone()), settings));
app.loading.loading_phases =
std::iter::once(LoadingPhase::ServerStarting).collect();
app.loading.last_active_phase = Some(LoadingPhase::ServerStarting);
app.loading.loading_progress = 0.25;
app.add_log(
format!("Starting server with {}...", model.display_name),
crate::config::LogLevel::Info,
);
}
} else {
let active_count = app
.model_states
.values()
.filter(|s| {
matches!(
s,
crate::models::ModelState::Loaded { .. }
| crate::models::ModelState::Loading
)
})
.count();
if let Some(max) = app.settings.max_concurrent_predictions
&& active_count as u32 >= max
{
app.add_log(format!("Limit reached: already {} model(s) loaded (Max Concurrent Predictions limit: {})", active_count, max), crate::config::LogLevel::Warning);
return;
}
app.ui.last_error_message = None;
app.pending.pending_api_load = Some((
model.display_name.clone(),
Some(model.path.to_string_lossy().to_string()),
));
app.loading.loading_phases =
std::iter::once(LoadingPhase::LoadingModel).collect();
app.loading.last_active_phase = Some(LoadingPhase::LoadingModel);
app.loading.loading_progress = 0.5;
app.add_log(
format!("Loading {} via API...", model.display_name),
crate::config::LogLevel::Info,
);
}
}
}
}
KeyCode::Char('u') => {
if let Some(idx) = app.selected_model_idx {
let model = app.models[idx].clone();
if let Some(crate::models::ModelState::Loaded { .. }) =
app.model_states.get(&model.display_name)
{
app.ui.global_mode = GlobalMode::Confirmation {
selected: false,
kind: crate::tui::app::ConfirmationKind::Unload,
};
app.pending.pending_api_unload = Some((
model.display_name.clone(),
Some(model.path.to_string_lossy().to_string()),
));
} else {
app.add_log(
format!("{} is not loaded", model.display_name),
crate::config::LogLevel::Warning,
);
}
} else if app.server.server_handle.is_some() {
app.add_log(
"Select a loaded model to unload",
crate::config::LogLevel::Warning,
);
} else if app.server_mode == crate::models::ServerMode::Router {
} else {
app.add_log(
"No model is currently loaded",
crate::config::LogLevel::Warning,
);
}
}
KeyCode::Delete if app.ui.active_panel == crate::tui::app::ActivePanel::Models => {
if let Some(model) = app.selected_model() {
let display_name = model.display_name.clone();
app.pending.pending_deletion = Some(model.path.clone());
app.ui.global_mode = GlobalMode::Confirmation {
selected: false,
kind: crate::tui::app::ConfirmationKind::Delete,
};
app.add_log(
format!("Delete confirmation for {} shown", display_name),
crate::config::LogLevel::Info,
);
} else {
app.add_log(
"No model selected to delete",
crate::config::LogLevel::Warning,
);
}
}
KeyCode::Char('d')
if key
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if app.ui.active_panel != crate::tui::app::ActivePanel::Models {
app.add_log(
"Press Tab to switch to Models panel, then Ctrl+D or Del to delete",
crate::config::LogLevel::Warning,
);
return;
}
if let Some(model) = app.selected_model() {
let display_name = model.display_name.clone();
app.pending.pending_deletion = Some(model.path.clone());
app.ui.global_mode = GlobalMode::Confirmation {
selected: false,
kind: crate::tui::app::ConfirmationKind::Delete,
};
app.add_log(
format!("Delete confirmation for {} shown", display_name),
crate::config::LogLevel::Info,
);
} else {
app.add_log(
"No model selected to delete",
crate::config::LogLevel::Warning,
);
}
}
_ => {}
}
}