@@ -5,18 +5,23 @@ use url::Url;
55use crate :: cli:: { ConnectProvider , ConnectionType } ;
66
77use super :: action:: Action ;
8- use super :: effect:: Effect ;
8+ use super :: effect:: { Effect , SaveData } ;
99use super :: providers:: { LLM_PROVIDERS , STT_PROVIDERS } ;
1010
1111#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
1212pub ( crate ) enum Step {
13- SelectType ,
1413 SelectProvider ,
1514 InputBaseUrl ,
1615 InputApiKey ,
1716 Done ,
1817}
1918
19+ #[ derive( Clone , Copy , Debug ) ]
20+ pub ( crate ) enum ListEntry {
21+ Header ( ConnectionType ) ,
22+ Provider ( ConnectionType , ConnectProvider ) ,
23+ }
24+
2025pub ( crate ) struct App {
2126 step : Step ,
2227 connection_type : Option < ConnectionType > ,
@@ -40,7 +45,7 @@ impl App {
4045 api_key : Option < String > ,
4146 ) -> ( Self , Vec < Effect > ) {
4247 let mut app = Self {
43- step : Step :: SelectType ,
48+ step : Step :: SelectProvider ,
4449 connection_type,
4550 provider,
4651 base_url,
@@ -100,23 +105,36 @@ impl App {
100105 & mut self . list_state
101106 }
102107
103- pub fn provider_list ( & self ) -> & ' static [ ConnectProvider ] {
108+ pub fn flat_entries ( & self ) -> Vec < ListEntry > {
104109 match self . connection_type {
105- Some ( ConnectionType :: Stt ) => STT_PROVIDERS ,
106- Some ( ConnectionType :: Llm ) => LLM_PROVIDERS ,
107- None => & [ ] ,
110+ Some ( ConnectionType :: Llm ) => LLM_PROVIDERS
111+ . iter ( )
112+ . map ( |& p| ListEntry :: Provider ( ConnectionType :: Llm , p) )
113+ . collect ( ) ,
114+ Some ( ConnectionType :: Stt ) => STT_PROVIDERS
115+ . iter ( )
116+ . map ( |& p| ListEntry :: Provider ( ConnectionType :: Stt , p) )
117+ . collect ( ) ,
118+ None => {
119+ let mut entries = Vec :: new ( ) ;
120+ entries. push ( ListEntry :: Header ( ConnectionType :: Llm ) ) ;
121+ for & p in LLM_PROVIDERS {
122+ entries. push ( ListEntry :: Provider ( ConnectionType :: Llm , p) ) ;
123+ }
124+ entries. push ( ListEntry :: Header ( ConnectionType :: Stt ) ) ;
125+ for & p in STT_PROVIDERS {
126+ entries. push ( ListEntry :: Provider ( ConnectionType :: Stt , p) ) ;
127+ }
128+ entries
129+ }
108130 }
109131 }
110132
111133 pub fn breadcrumb ( & self ) -> String {
112- let mut parts = Vec :: new ( ) ;
113- if let Some ( ct) = self . connection_type {
114- parts. push ( ct. to_string ( ) ) ;
115- }
116- if let Some ( p) = self . provider {
117- parts. push ( p. to_string ( ) ) ;
134+ match self . provider {
135+ Some ( p) => p. to_string ( ) ,
136+ None => String :: new ( ) ,
118137 }
119- parts. join ( " > " )
120138 }
121139
122140 fn handle_key ( & mut self , key : KeyEvent ) -> Vec < Effect > {
@@ -127,7 +145,7 @@ impl App {
127145 }
128146
129147 match self . step {
130- Step :: SelectType | Step :: SelectProvider => self . handle_list_key ( key) ,
148+ Step :: SelectProvider => self . handle_list_key ( key) ,
131149 Step :: InputBaseUrl | Step :: InputApiKey => self . handle_input_key ( key) ,
132150 Step :: Done => Vec :: new ( ) ,
133151 }
@@ -151,27 +169,36 @@ impl App {
151169 fn handle_list_key ( & mut self , key : KeyEvent ) -> Vec < Effect > {
152170 match key. code {
153171 KeyCode :: Up | KeyCode :: Char ( 'k' ) => {
154- self . list_state . select_previous ( ) ;
172+ self . list_navigate ( - 1 ) ;
155173 Vec :: new ( )
156174 }
157175 KeyCode :: Down | KeyCode :: Char ( 'j' ) => {
158- self . list_state . select_next ( ) ;
176+ self . list_navigate ( 1 ) ;
159177 Vec :: new ( )
160178 }
161179 KeyCode :: Enter => {
162180 self . confirm_list_selection ( ) ;
163- self . step = match self . step {
164- Step :: SelectType => Step :: SelectProvider ,
165- Step :: SelectProvider => Step :: InputBaseUrl ,
166- _ => unreachable ! ( ) ,
167- } ;
181+ self . step = Step :: InputBaseUrl ;
168182 self . advance ( )
169183 }
170184 KeyCode :: Char ( 'q' ) => vec ! [ Effect :: Exit ] ,
171185 _ => Vec :: new ( ) ,
172186 }
173187 }
174188
189+ fn list_navigate ( & mut self , direction : isize ) {
190+ let entries = self . flat_entries ( ) ;
191+ let current = self . list_state . selected ( ) . unwrap_or ( 0 ) ;
192+ let mut next = current as isize + direction;
193+ while next >= 0 && ( next as usize ) < entries. len ( ) {
194+ if matches ! ( entries[ next as usize ] , ListEntry :: Provider ( ..) ) {
195+ self . list_state . select ( Some ( next as usize ) ) ;
196+ return ;
197+ }
198+ next += direction;
199+ }
200+ }
201+
175202 fn handle_input_key ( & mut self , key : KeyEvent ) -> Vec < Effect > {
176203 match key. code {
177204 KeyCode :: Enter => {
@@ -228,21 +255,10 @@ impl App {
228255
229256 fn confirm_list_selection ( & mut self ) {
230257 let idx = self . list_state . selected ( ) . unwrap_or ( 0 ) ;
231- match self . step {
232- Step :: SelectType => {
233- self . connection_type = Some ( if idx == 0 {
234- ConnectionType :: Stt
235- } else {
236- ConnectionType :: Llm
237- } ) ;
238- }
239- Step :: SelectProvider => {
240- let providers = self . provider_list ( ) ;
241- if idx < providers. len ( ) {
242- self . provider = Some ( providers[ idx] ) ;
243- }
244- }
245- _ => { }
258+ let entries = self . flat_entries ( ) ;
259+ if let Some ( ListEntry :: Provider ( ct, provider) ) = entries. get ( idx) {
260+ self . connection_type = Some ( * ct) ;
261+ self . provider = Some ( * provider) ;
246262 }
247263 }
248264
@@ -268,27 +284,28 @@ impl App {
268284 Ok ( ( ) )
269285 }
270286
287+ fn first_selectable_index ( & self ) -> usize {
288+ self . flat_entries ( )
289+ . iter ( )
290+ . position ( |e| matches ! ( e, ListEntry :: Provider ( ..) ) )
291+ . unwrap_or ( 0 )
292+ }
293+
271294 fn advance ( & mut self ) -> Vec < Effect > {
272295 loop {
273296 match self . step {
274- Step :: SelectType => {
275- if self . connection_type . is_some ( ) {
276- self . step = Step :: SelectProvider ;
277- continue ;
278- }
279- self . list_state = ListState :: default ( ) . with_selected ( Some ( 0 ) ) ;
280- return Vec :: new ( ) ;
281- }
282297 Step :: SelectProvider => {
283298 if let Some ( provider) = self . provider {
284- let ct = self . connection_type . unwrap ( ) ;
285- if provider. valid_for ( ct) {
286- self . step = Step :: InputBaseUrl ;
287- continue ;
299+ if let Some ( ct) = self . connection_type {
300+ if provider. valid_for ( ct) {
301+ self . step = Step :: InputBaseUrl ;
302+ continue ;
303+ }
288304 }
289305 self . provider = None ;
290306 }
291- self . list_state = ListState :: default ( ) . with_selected ( Some ( 0 ) ) ;
307+ let first = self . first_selectable_index ( ) ;
308+ self . list_state = ListState :: default ( ) . with_selected ( Some ( first) ) ;
292309 return Vec :: new ( ) ;
293310 }
294311 Step :: InputBaseUrl => {
@@ -322,12 +339,12 @@ impl App {
322339 return Vec :: new ( ) ;
323340 }
324341 Step :: Done => {
325- return vec ! [ Effect :: Save {
342+ return vec ! [ Effect :: Save ( SaveData {
326343 connection_type: self . connection_type. unwrap( ) ,
327344 provider: self . provider. unwrap( ) ,
328345 base_url: self . base_url. clone( ) ,
329346 api_key: self . api_key. clone( ) ,
330- } ] ;
347+ } ) ] ;
331348 }
332349 }
333350 }
@@ -357,13 +374,13 @@ mod tests {
357374 Some ( "key123" . to_string ( ) ) ,
358375 ) ;
359376 assert_eq ! ( app. step( ) , Step :: Done ) ;
360- assert ! ( matches!( effects. as_slice( ) , [ Effect :: Save { .. } ] ) ) ;
377+ assert ! ( matches!( effects. as_slice( ) , [ Effect :: Save ( _ ) ] ) ) ;
361378 }
362379
363380 #[ test]
364- fn no_args_starts_at_select_type ( ) {
381+ fn no_args_starts_at_select_provider ( ) {
365382 let ( app, effects) = App :: new ( None , None , None , None ) ;
366- assert_eq ! ( app. step( ) , Step :: SelectType ) ;
383+ assert_eq ! ( app. step( ) , Step :: SelectProvider ) ;
367384 assert ! ( effects. is_empty( ) ) ;
368385 }
369386
@@ -399,13 +416,15 @@ mod tests {
399416 }
400417
401418 #[ test]
402- fn select_type_then_advance ( ) {
419+ fn select_provider_from_flat_list ( ) {
403420 let ( mut app, _) = App :: new ( None , None , None , None ) ;
404- assert_eq ! ( app. step( ) , Step :: SelectType ) ;
421+ assert_eq ! ( app. step( ) , Step :: SelectProvider ) ;
422+ // First selectable entry is the first LLM provider (index 1, after the header)
423+ assert_eq ! ( app. list_state_mut( ) . selected( ) , Some ( 1 ) ) ;
405424
406425 let effects = app. dispatch ( Action :: Key ( KeyEvent :: from ( KeyCode :: Enter ) ) ) ;
407426 assert ! ( effects. is_empty( ) ) ;
408- assert_eq ! ( app. step( ) , Step :: SelectProvider ) ;
427+ assert_eq ! ( app. step( ) , Step :: InputBaseUrl ) ;
409428 }
410429
411430 #[ test]
0 commit comments