llm-manager 1.6.6

Terminal UI for managing LLMs
Documentation
use super::types::{ActivePanel, App, ModelsMode};

impl App {
    /// Check if a panel is visible.
    pub fn is_panel_visible(&self, index: u8) -> bool {
        self.ui.panel_visibility & (1 << index) != 0
    }

    /// Toggle visibility of a panel.
    pub fn toggle_panel_visibility(&mut self, index: u8) {
        self.ui.panel_visibility ^= 1 << index;
        // If hiding the log while expanded, collapse it.
        if index == 5 && !self.is_panel_visible(5) {
            self.log.log_expanded = false;
        }
    }

    pub fn hide_all_panels(&mut self) {
        self.ui.panel_visibility &= !(1 << 1);
        self.ui.panel_visibility &= !(1 << 2);
        self.ui.panel_visibility &= !(1 << 3);
        self.ui.panel_visibility &= !(1 << 4);
        self.ui.panel_visibility &= !(1 << 5);
    }

    /// Return a list of all currently visible and focusable panels in logical order.
    pub fn get_visible_panels(&self) -> Vec<ActivePanel> {
        let mut visible = Vec::new();

        // 1. Models (Left Top)
        if self.is_panel_visible(0) {
            visible.push(ActivePanel::Models);
        }

        // 3. Right Panel (README / Settings / Profiles / Presets)
        let (is_search, is_files, is_bench_tune, _show_readme) = match &self.models_mode {
            ModelsMode::Search { show_readme, .. } => (true, false, false, *show_readme),
            ModelsMode::Files { .. } => (false, true, false, true),
            ModelsMode::BenchTune => (false, false, true, false),
            _ => (false, false, false, false),
        };

        if self.ui.active_panel == ActivePanel::Profiles {
            visible.push(ActivePanel::Profiles);
        } else if self.ui.active_panel == ActivePanel::SystemPromptPresets {
            visible.push(ActivePanel::SystemPromptPresets);
        } else if is_search || is_files {
            visible.push(ActivePanel::SearchReadme);
        } else {
            if self.is_panel_visible(1) && self.server.server_handle.is_none() && !is_bench_tune {
                visible.push(ActivePanel::ServerSettings);
            }
            if self.is_panel_visible(3) {
                visible.push(ActivePanel::LlmSettings);
            }
        }

        // 4. Active Model (Bottom Middle) — read-only, not focusable

        // 5. Log (Bottom)
        if self.is_panel_visible(5) {
            visible.push(ActivePanel::Log);
        }

        // 6. Downloads (Bottom, shown when downloading)
        if self.download.downloading {
            visible.push(ActivePanel::Downloads);
        }

        visible
    }

    fn focus_direction(&mut self, forward: bool) {
        let visible = self.get_visible_panels();
        let len = visible.len();
        if len == 0 {
            return;
        }

        let current_idx = visible
            .iter()
            .position(|&p| p == self.ui.active_panel)
            .unwrap_or(0);
        let next_idx = if forward {
            (current_idx + 1) % len
        } else {
            (current_idx + len - 1) % len
        };
        self.ui.active_panel = visible[next_idx];
    }

    pub fn focus_next(&mut self) {
        self.focus_direction(true);
    }

    pub fn focus_prev(&mut self) {
        self.focus_direction(false);
    }
}