atomcode_tuix/modals/
model_picker.rs1use anyhow::Result;
10use atomcode_core::config::Config;
11use crossterm::event::{KeyCode, KeyModifiers};
12
13use super::{Modal, ModalAction};
14use crate::event_loop::{build_status, save_and_reload, Buffer, LoopCtx};
15use crate::render::{MenuPayload, Renderer, UiLine};
16use crate::state::UiState;
17
18pub struct ModelPicker {
19 pub providers: Vec<String>,
20 pub selected: usize,
21}
22
23impl ModelPicker {
24 pub fn open(config: &Config) -> Self {
25 let mut providers: Vec<String> = config.providers.keys().cloned().collect();
26 providers.sort();
27 let cur = config.default_provider.clone();
29 if let Some(idx) = providers.iter().position(|p| *p == cur) {
30 providers.swap(0, idx);
31 }
32 Self {
33 providers,
34 selected: 0,
35 }
36 }
37}
38
39impl Modal for ModelPicker {
40 fn handle_key(
41 &mut self,
42 code: KeyCode,
43 _mods: KeyModifiers,
44 buf: &mut Buffer,
45 state: &mut UiState,
46 ctx: &mut LoopCtx,
47 renderer: &mut dyn Renderer,
48 ) -> Result<ModalAction> {
49 match code {
50 KeyCode::Up => {
51 self.selected = self.selected.saturating_sub(1);
52 self.draw(buf, state, ctx, renderer);
53 Ok(ModalAction::Continue)
54 }
55 KeyCode::Down => {
56 let max = self.providers.len().saturating_sub(1);
57 if self.selected < max {
58 self.selected += 1;
59 }
60 self.draw(buf, state, ctx, renderer);
61 Ok(ModalAction::Continue)
62 }
63 KeyCode::Enter => {
64 let chosen = self.providers[self.selected].clone();
65 let display = ctx
66 .config
67 .providers
68 .get(&chosen)
69 .map(|p| p.model.clone())
70 .unwrap_or_else(|| chosen.clone());
71 ctx.config.default_provider = chosen.clone();
72 ctx.model_name = display.clone();
73 save_and_reload(ctx, renderer);
77 if let Ok(mut g) = ctx.monitor_warning.lock() {
82 *g = None;
83 }
84 if let Ok(mut g) = ctx.usage_slot.lock() {
90 *g = None;
91 }
92 if crate::event_loop::monitor::is_codingplan_provider(&chosen) {
96 ctx.monitor_last_check_at = Some(std::time::Instant::now());
97 crate::event_loop::monitor::spawn_check(
98 ctx.config.clone(),
99 ctx.model_name.clone(),
100 ctx.monitor_warning.clone(),
101 ctx.wake_tx.clone(),
102 );
103 ctx.usage_last_check_at = Some(std::time::Instant::now());
107 crate::event_loop::usage_monitor::spawn_check(
108 ctx.usage_slot.clone(),
109 ctx.wake_tx.clone(),
110 );
111 }
112 renderer.render(UiLine::CommandOutput(
113 crate::i18n::t(crate::i18n::Msg::ModelSwitched { provider: &chosen, model: &display }).into_owned(),
114 ));
115 renderer.flush();
116 Ok(ModalAction::Close)
117 }
118 KeyCode::Esc => Ok(ModalAction::Close),
119 _ => Ok(ModalAction::Continue),
120 }
121 }
122
123 fn draw(&self, buf: &Buffer, state: &UiState, ctx: &LoopCtx, renderer: &mut dyn Renderer) {
124 let items: Vec<(String, String)> = self
125 .providers
126 .iter()
127 .map(|name| {
128 let desc = ctx
129 .config
130 .providers
131 .get(name)
132 .map(|c| format!("{} · {}", c.provider_type, c.model))
133 .unwrap_or_default();
134 (name.clone(), desc)
135 })
136 .collect();
137 let payload = MenuPayload {
138 items,
139 selected: self.selected,
140 kind: crate::render::MenuKind::SlashCommand,
141 };
142 renderer.render(UiLine::InputPrompt {
143 buf: buf.text.clone(),
144 cursor_byte: buf.cursor,
145 menu: Some(payload),
146 status: build_status(state, ctx),
147 attachments: Vec::new(),
148 });
149 renderer.flush();
150 }
151}