llm-manager 1.1.5

Terminal UI for managing LLMs
Documentation
use std::pin::Pin;
use std::future::Future;

use crossterm::event::{KeyCode, KeyEvent};

use super::super::helpers::{sync_global_settings, picker_nav_up, picker_nav_down};
use crate::tui::app::{App, ConfirmationKind, GlobalMode};

use super::OverlayHandler;

pub struct BackendPickerHandler;

impl OverlayHandler for BackendPickerHandler {
    fn name(&self) -> &'static str {
        "BackendPicker"
    }

    fn can_handle(&self, mode: &GlobalMode) -> bool {
        matches!(mode, GlobalMode::BackendPicker { .. })
    }

    fn handle<'a>(
        &'a self,
        app: &'a mut App,
        key: KeyEvent,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move {
            if let GlobalMode::BackendPicker { entries, selected } = &mut app.ui.global_mode {
                match key.code {
                    KeyCode::Esc => {
                        app.ui.global_mode = GlobalMode::Normal;
                    }
                    KeyCode::Up | KeyCode::Char('k') => picker_nav_up(selected),
                    KeyCode::Down | KeyCode::Char('j') => picker_nav_down(selected, entries.len()),
                    KeyCode::Enter => {
                        let (backend, tag) = entries[*selected].clone();
                        app.settings.backend = backend;
                        app.settings.set_active_backend_version(tag.clone());
                        if !crate::backend::hub::is_backend_version_installed(backend, tag.as_deref()) {
                            app.pending.backend_resolving = true;
                            let tag_param = tag.clone();
                            if app.download.download_rx.is_none() {
                                let (tx, rx) = tokio::sync::broadcast::channel(10);
                                app.download.download_tx = Some(tx);
                                app.download.download_rx = Some(rx);
                            }
                            let (log_tx, log_rx) = tokio::sync::mpsc::channel(100);
                            app.server.server_log_rx = Some(log_rx);
                            let tx = app.download.download_tx.clone();
                            let handle = tokio::spawn(async move {
                                crate::backend::hub::resolve_backend_binary(
                                    backend,
                                    tag_param.as_deref(),
                                    Some(log_tx),
                                    tx,
                                )
                                .await
                                .map_err(|e| e.to_string())
                            });
                            app.pending.backend_resolve_handle = Some(handle);
                        } else {
                            app.pending.backend_resolving = false;
                        }
                        app.ui.global_mode = GlobalMode::Normal;
                        sync_global_settings(app);
                    }
                    KeyCode::Char('d') | KeyCode::Delete => {
                        if let Some((backend, Some(tag))) = entries.get(*selected) {
                            let backend_slug = backend.slug().to_string();
                            app.ui.global_mode = GlobalMode::Confirmation {
                                selected: false,
                                kind: ConfirmationKind::DeleteBackend,
                                display_name: format!("{} ({})", backend_slug, tag),
                                detail: Some(format!("{}:{}", backend_slug, tag)),
                            };
                        }
                    }
                    KeyCode::Char('h')
                        if key
                            .modifiers
                            .contains(crossterm::event::KeyModifiers::CONTROL) =>
                    {
                        app.ui.global_mode = GlobalMode::Normal;
                    }
                    _ => {}
                }
            }
        })
    }
}