llm 1.3.8

A Rust library unifying multiple LLM backends.
Documentation
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;

#[derive(Debug, Clone)]
pub struct PickerItem {
    pub id: String,
    pub label: String,
    pub meta: Option<String>,
    pub badges: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct PickerState {
    pub title: String,
    pub query: String,
    pub items: Vec<PickerItem>,
    pub filtered: Vec<PickerItem>,
    pub selected: usize,
}

impl PickerState {
    pub fn new(title: impl Into<String>, items: Vec<PickerItem>) -> Self {
        let mut state = Self {
            title: title.into(),
            query: String::new(),
            filtered: items.clone(),
            items,
            selected: 0,
        };
        state.refresh();
        state
    }

    pub fn push_query(&mut self, ch: char) {
        self.query.push(ch);
        self.refresh();
    }

    pub fn pop_query(&mut self) {
        self.query.pop();
        self.refresh();
    }

    pub fn next(&mut self) {
        if !self.filtered.is_empty() {
            self.selected = (self.selected + 1).min(self.filtered.len() - 1);
        }
    }

    pub fn prev(&mut self) {
        if self.selected > 0 {
            self.selected -= 1;
        }
    }

    pub fn selected_item(&self) -> Option<&PickerItem> {
        self.filtered.get(self.selected)
    }

    fn refresh(&mut self) {
        if self.query.is_empty() {
            self.filtered = self.items.clone();
            return;
        }
        let matcher = SkimMatcherV2::default();
        let mut scored: Vec<(i64, PickerItem)> = self
            .items
            .iter()
            .filter_map(|item| {
                matcher
                    .fuzzy_match(&item.label, &self.query)
                    .map(|score| (score, item.clone()))
            })
            .collect();
        scored.sort_by(|a, b| b.0.cmp(&a.0));
        self.filtered = scored.into_iter().map(|(_, item)| item).collect();
        self.selected = self.selected.min(self.filtered.len().saturating_sub(1));
    }
}