koda_cli/widgets/
provider_menu.rs1use super::dropdown::DropdownItem;
6
7#[derive(Clone, Debug)]
9pub struct ProviderItem {
10 pub key: &'static str,
12 pub name: &'static str,
14 pub local: bool,
16 pub key_set: bool,
19}
20
21impl DropdownItem for ProviderItem {
22 fn label(&self) -> &str {
23 self.name
24 }
25 fn description(&self) -> String {
26 let mut desc = if self.local {
27 "Local, no API key".to_string()
28 } else {
29 String::new()
30 };
31 if self.key_set {
32 if !desc.is_empty() {
33 desc.push(' ');
34 }
35 desc.push('\u{2705}');
36 }
37 desc
38 }
39 fn matches_filter(&self, filter: &str) -> bool {
40 let f = filter.to_lowercase();
41 self.name.to_lowercase().contains(&f)
42 || self.key.to_lowercase().contains(&f)
43 || (self.local && "local".contains(&f))
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn key_set_shows_checkmark() {
53 let item = ProviderItem {
54 key: "anthropic",
55 name: "Anthropic",
56 local: false,
57 key_set: true,
58 };
59 assert!(item.description().contains('\u{2705}'));
60 }
61
62 #[test]
63 fn no_key_no_marker() {
64 let item = ProviderItem {
65 key: "openai",
66 name: "OpenAI",
67 local: false,
68 key_set: false,
69 };
70 assert!(item.description().is_empty());
71 }
72
73 #[test]
74 fn local_shows_description() {
75 let item = ProviderItem {
76 key: "ollama",
77 name: "Ollama",
78 local: true,
79 key_set: false,
80 };
81 assert!(item.description().contains("Local, no API key"));
82 }
83
84 #[test]
85 fn filter_matches_key_name_local() {
86 let item = ProviderItem {
87 key: "lmstudio",
88 name: "LM Studio",
89 local: true,
90 key_set: false,
91 };
92 assert!(item.matches_filter("lm"));
93 assert!(item.matches_filter("Studio"));
94 assert!(item.matches_filter("local"));
95 assert!(!item.matches_filter("gemini"));
96 }
97}