diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 445d6a6df6b7395997d9e0bab6ccff153dfe99c5..d0d333148a447e8c9a0397d24d4adb4850bc02d2 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -943,7 +943,7 @@ impl AssistantPanel { }); } else { let configuration = cx.new_view(|cx| { - let mut view = ConfigurationView::new(self.focus_handle(cx), cx); + let mut view = ConfigurationView::new(cx); if let Some(provider) = provider { view.set_active_tab(provider, cx); } @@ -3044,11 +3044,6 @@ impl Item for ContextHistory { } } -pub struct ConfigurationView { - fallback_handle: FocusHandle, - active_tab: Option, -} - struct ActiveTab { provider: Arc, configuration_prompt: AnyView, @@ -3067,12 +3062,37 @@ impl ActiveTab { } } +pub struct ConfigurationView { + focus_handle: FocusHandle, + active_tab: Option, +} + impl ConfigurationView { - fn new(fallback_handle: FocusHandle, _cx: &mut ViewContext) -> Self { - Self { - fallback_handle, + fn new(cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + cx.on_focus(&focus_handle, |this, cx| { + if let Some(focus_handle) = this + .active_tab + .as_ref() + .and_then(|tab| tab.focus_handle.as_ref()) + { + focus_handle.focus(cx); + } + }) + .detach(); + + let mut this = Self { + focus_handle, active_tab: None, + }; + + let providers = LanguageModelRegistry::read_global(cx).providers(); + if !providers.is_empty() { + this.set_active_tab(providers[0].clone(), cx); } + + this } fn set_active_tab( @@ -3085,7 +3105,7 @@ impl ConfigurationView { if let Some(focus_handle) = &focus_handle { focus_handle.focus(cx); } else { - self.fallback_handle.focus(cx); + self.focus_handle.focus(cx); } let load_credentials = provider.authenticate(cx); @@ -3224,11 +3244,6 @@ impl ConfigurationView { impl Render for ConfigurationView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let providers = LanguageModelRegistry::read_global(cx).providers(); - - if self.active_tab.is_none() && !providers.is_empty() { - self.set_active_tab(providers[0].clone(), cx); - } - let tabs = h_flex().mx_neg_1().gap_3().children( providers .iter() @@ -3237,6 +3252,7 @@ impl Render for ConfigurationView { v_flex() .id("assistant-configuration-view") + .track_focus(&self.focus_handle) .w_full() .min_h_full() .p(Spacing::XXLarge.rems(cx)) @@ -3258,7 +3274,12 @@ impl Render for ConfigurationView { .color(Color::Muted), ) .child(tabs) - .children(self.render_active_tab_view(cx)), + .when(self.active_tab.is_some(), |this| { + this.children(self.render_active_tab_view(cx)) + }) + .when(self.active_tab.is_none(), |this| { + this.child(Label::new("No providers configured").color(Color::Warning)) + }), ) } } @@ -3271,10 +3292,7 @@ impl EventEmitter for ConfigurationView {} impl FocusableView for ConfigurationView { fn focus_handle(&self, _: &AppContext) -> FocusHandle { - self.active_tab - .as_ref() - .and_then(|tab| tab.focus_handle.clone()) - .unwrap_or(self.fallback_handle.clone()) + self.focus_handle.clone() } } diff --git a/crates/language_model/src/provider/anthropic.rs b/crates/language_model/src/provider/anthropic.rs index a9b9ec686eaf9b458201c720054f022dfa9497ff..9935bd7a7fe2768a7b13758236011371fc02c9c1 100644 --- a/crates/language_model/src/provider/anthropic.rs +++ b/crates/language_model/src/provider/anthropic.rs @@ -383,18 +383,22 @@ impl LanguageModel for AnthropicModel { } struct ConfigurationView { + focus_handle: FocusHandle, api_key_editor: View, state: gpui::Model, } -impl FocusableView for ConfigurationView { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.api_key_editor.read(cx).focus_handle(cx) - } -} - impl ConfigurationView { - fn new(state: gpui::Model, cx: &mut WindowContext) -> Self { + fn new(state: gpui::Model, cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + cx.on_focus(&focus_handle, |this, cx| { + if this.should_render_editor(cx) { + this.api_key_editor.read(cx).focus_handle(cx).focus(cx) + } + }) + .detach(); + Self { api_key_editor: cx.new_view(|cx| { let mut editor = Editor::single_line(cx); @@ -404,6 +408,7 @@ impl ConfigurationView { ); editor }), + focus_handle, state, } } @@ -453,6 +458,16 @@ impl ConfigurationView { }, ) } + + fn should_render_editor(&self, cx: &mut ViewContext) -> bool { + !self.state.read(cx).is_authenticated() + } +} + +impl FocusableView for ConfigurationView { + fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { + self.focus_handle.clone() + } } impl Render for ConfigurationView { @@ -464,26 +479,10 @@ impl Render for ConfigurationView { "Paste your Anthropic API key below and hit enter to use the assistant:", ]; - if self.state.read(cx).is_authenticated() { - h_flex() - .size_full() - .justify_between() - .child( - h_flex() - .gap_2() - .child(Indicator::dot().color(Color::Success)) - .child(Label::new("API Key configured").size(LabelSize::Small)), - ) - .child( - Button::new("reset-key", "Reset key") - .icon(Some(IconName::Trash)) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), - ) - .into_any() - } else { + if self.should_render_editor(cx) { v_flex() + .id("anthropic-configuration-view") + .track_focus(&self.focus_handle) .size_full() .on_action(cx.listener(Self::save_api_key)) .children( @@ -506,6 +505,26 @@ impl Render for ConfigurationView { .size(LabelSize::Small), ) .into_any() + } else { + h_flex() + .id("anthropic-configuration-view") + .track_focus(&self.focus_handle) + .size_full() + .justify_between() + .child( + h_flex() + .gap_2() + .child(Indicator::dot().color(Color::Success)) + .child(Label::new("API Key configured").size(LabelSize::Small)), + ) + .child( + Button::new("reset-key", "Reset key") + .icon(Some(IconName::Trash)) + .icon_size(IconSize::Small) + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), + ) + .into_any() } } } diff --git a/crates/language_model/src/provider/google.rs b/crates/language_model/src/provider/google.rs index b84bf87fdbce5a52d7966c8c1a1c1ba5ee70de23..3d907389c0928913b17820fb046968db3d950d81 100644 --- a/crates/language_model/src/provider/google.rs +++ b/crates/language_model/src/provider/google.rs @@ -292,12 +292,22 @@ impl LanguageModel for GoogleLanguageModel { } struct ConfigurationView { + focus_handle: FocusHandle, api_key_editor: View, state: gpui::Model, } impl ConfigurationView { - fn new(state: gpui::Model, cx: &mut WindowContext) -> Self { + fn new(state: gpui::Model, cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + cx.on_focus(&focus_handle, |this, cx| { + if this.should_render_editor(cx) { + this.api_key_editor.read(cx).focus_handle(cx).focus(cx) + } + }) + .detach(); + Self { api_key_editor: cx.new_view(|cx| { let mut editor = Editor::single_line(cx); @@ -305,6 +315,7 @@ impl ConfigurationView { editor }), state, + focus_handle, } } @@ -362,11 +373,15 @@ impl ConfigurationView { }, ) } + + fn should_render_editor(&self, cx: &mut ViewContext) -> bool { + !self.state.read(cx).is_authenticated() + } } impl FocusableView for ConfigurationView { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.api_key_editor.read(cx).focus_handle(cx) + fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { + self.focus_handle.clone() } } @@ -379,26 +394,10 @@ impl Render for ConfigurationView { "Paste your Google AI API key below and hit enter to use the assistant:", ]; - if self.state.read(cx).is_authenticated() { - h_flex() - .size_full() - .justify_between() - .child( - h_flex() - .gap_2() - .child(Indicator::dot().color(Color::Success)) - .child(Label::new("API Key configured").size(LabelSize::Small)), - ) - .child( - Button::new("reset-key", "Reset key") - .icon(Some(IconName::Trash)) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), - ) - .into_any() - } else { + if self.should_render_editor(cx) { v_flex() + .id("google-ai-configuration-view") + .track_focus(&self.focus_handle) .size_full() .on_action(cx.listener(Self::save_api_key)) .children( @@ -421,6 +420,26 @@ impl Render for ConfigurationView { .size(LabelSize::Small), ) .into_any() + } else { + h_flex() + .id("google-ai-configuration-view") + .track_focus(&self.focus_handle) + .size_full() + .justify_between() + .child( + h_flex() + .gap_2() + .child(Indicator::dot().color(Color::Success)) + .child(Label::new("API Key configured").size(LabelSize::Small)), + ) + .child( + Button::new("reset-key", "Reset key") + .icon(Some(IconName::Trash)) + .icon_size(IconSize::Small) + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), + ) + .into_any() } } } diff --git a/crates/language_model/src/provider/open_ai.rs b/crates/language_model/src/provider/open_ai.rs index b7842dd72b9419fb5b8b4b5c08c284750bf51c02..b6daa77360056c40de0c63b0ea4da8565b048d78 100644 --- a/crates/language_model/src/provider/open_ai.rs +++ b/crates/language_model/src/provider/open_ai.rs @@ -302,12 +302,22 @@ pub fn count_open_ai_tokens( } struct ConfigurationView { + focus_handle: FocusHandle, api_key_editor: View, state: gpui::Model, } impl ConfigurationView { - fn new(state: gpui::Model, cx: &mut WindowContext) -> Self { + fn new(state: gpui::Model, cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + cx.on_focus(&focus_handle, |this, cx| { + if this.should_render_editor(cx) { + this.api_key_editor.read(cx).focus_handle(cx).focus(cx) + } + }) + .detach(); + Self { api_key_editor: cx.new_view(|cx| { let mut editor = Editor::single_line(cx); @@ -318,6 +328,7 @@ impl ConfigurationView { editor }), state, + focus_handle, } } @@ -375,11 +386,15 @@ impl ConfigurationView { }, ) } + + fn should_render_editor(&self, cx: &mut ViewContext) -> bool { + !self.state.read(cx).is_authenticated() + } } impl FocusableView for ConfigurationView { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.api_key_editor.read(cx).focus_handle(cx) + fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { + self.focus_handle.clone() } } @@ -394,26 +409,10 @@ impl Render for ConfigurationView { "Paste your OpenAI API key below and hit enter to use the assistant:", ]; - if self.state.read(cx).is_authenticated() { - h_flex() - .size_full() - .justify_between() - .child( - h_flex() - .gap_2() - .child(Indicator::dot().color(Color::Success)) - .child(Label::new("API Key configured").size(LabelSize::Small)), - ) - .child( - Button::new("reset-key", "Reset key") - .icon(Some(IconName::Trash)) - .icon_size(IconSize::Small) - .icon_position(IconPosition::Start) - .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), - ) - .into_any() - } else { + if self.should_render_editor(cx) { v_flex() + .id("openai-configuration-view") + .track_focus(&self.focus_handle) .size_full() .on_action(cx.listener(Self::save_api_key)) .children( @@ -436,6 +435,26 @@ impl Render for ConfigurationView { .size(LabelSize::Small), ) .into_any() + } else { + h_flex() + .id("openai-configuration-view") + .track_focus(&self.focus_handle) + .size_full() + .justify_between() + .child( + h_flex() + .gap_2() + .child(Indicator::dot().color(Color::Success)) + .child(Label::new("API Key configured").size(LabelSize::Small)), + ) + .child( + Button::new("reset-key", "Reset key") + .icon(Some(IconName::Trash)) + .icon_size(IconSize::Small) + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), + ) + .into_any() } } }