chabeau 0.7.1

A full-screen terminal chat interface that connects to various AI APIs for real-time conversations
Documentation
use crate::core::config::data::Config;
use std::collections::HashMap;

pub(crate) trait ManagedItem: Clone {
    fn id(&self) -> &str;
}

pub(crate) struct SelectionState<T: ManagedItem> {
    items: Vec<T>,
    active: Option<T>,
    defaults: HashMap<(String, String), String>,
    set_default_fn: fn(&mut Config, String, String, String),
    unset_default_fn: fn(&mut Config, &str, &str),
    item_label: &'static str,
}

impl<T: ManagedItem> SelectionState<T> {
    pub(crate) fn load_from_config(
        config: &Config,
        items_getter: impl Fn(&Config) -> &Vec<T>,
        defaults_getter: impl Fn(&Config) -> &HashMap<String, HashMap<String, String>>,
        set_default_fn: fn(&mut Config, String, String, String),
        unset_default_fn: fn(&mut Config, &str, &str),
        item_label: &'static str,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let mut defaults = HashMap::new();
        for (provider, models) in defaults_getter(config) {
            let provider_key = provider.to_lowercase();
            for (model, item_id) in models {
                defaults.insert((provider_key.clone(), model.clone()), item_id.clone());
            }
        }

        Ok(Self {
            items: items_getter(config).clone(),
            active: None,
            defaults,
            set_default_fn,
            unset_default_fn,
            item_label,
        })
    }

    pub(crate) fn items(&self) -> &Vec<T> {
        &self.items
    }

    pub(crate) fn find_by_id(&self, id: &str) -> Option<&T> {
        self.items.iter().find(|item| item.id() == id)
    }

    pub(crate) fn set_active(&mut self, item_id: &str) -> Result<(), String> {
        if let Some(item) = self.items.iter().find(|item| item.id() == item_id).cloned() {
            self.active = Some(item);
            Ok(())
        } else {
            let available_ids: Vec<&str> = self.items.iter().map(|item| item.id()).collect();
            Err(format!(
                "{} '{}' not found. Available {}s: {}",
                self.item_label,
                item_id,
                self.item_label.to_lowercase(),
                available_ids.join(", ")
            ))
        }
    }

    pub(crate) fn clear_active(&mut self) {
        self.active = None;
    }

    pub(crate) fn get_active(&self) -> Option<&T> {
        self.active.as_ref()
    }

    pub(crate) fn get_default_for_provider_model(
        &self,
        provider: &str,
        model: &str,
    ) -> Option<&str> {
        let key = (provider.to_lowercase(), model.to_string());
        self.defaults.get(&key).map(|s| s.as_str())
    }

    pub(crate) fn set_default_persistent(
        &mut self,
        provider: &str,
        model: &str,
        item_id: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let key = (provider.to_lowercase(), model.to_string());
        self.defaults.insert(key, item_id.to_string());

        let provider = provider.to_string();
        let model = model.to_string();
        let item_id = item_id.to_string();
        let setter = self.set_default_fn;

        Config::mutate(move |config| {
            setter(config, provider.clone(), model.clone(), item_id.clone());
            Ok(())
        })?;

        Ok(())
    }

    pub(crate) fn unset_default_persistent(
        &mut self,
        provider: &str,
        model: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let key = (provider.to_lowercase(), model.to_string());
        self.defaults.remove(&key);

        let provider = provider.to_string();
        let model = model.to_string();
        let unsetter = self.unset_default_fn;

        Config::mutate(move |config| {
            unsetter(config, &provider, &model);
            Ok(())
        })?;

        Ok(())
    }
}