Cargo.lock 🔗
@@ -109,6 +109,7 @@ dependencies = [
"gpui",
"isahc",
"language",
+ "menu",
"schemars",
"search",
"serde",
Antonio Scandurra created
Cargo.lock | 1
assets/settings/default.json | 2
crates/ai/Cargo.toml | 1
crates/ai/src/assistant.rs | 65 +++++++++++++++++++++++++++++++-
crates/theme/src/theme.rs | 2 +
styles/src/styleTree/assistant.ts | 22 ++++++++++
6 files changed, 88 insertions(+), 5 deletions(-)
@@ -109,6 +109,7 @@ dependencies = [
"gpui",
"isahc",
"language",
+ "menu",
"schemars",
"search",
"serde",
@@ -85,7 +85,7 @@
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right",
// Default width when the assistant is docked to the left or right.
- "default_width": 480,
+ "default_width": 450,
// Default height when the assistant is docked to the bottom.
"default_height": 320,
// OpenAI API key.
@@ -15,6 +15,7 @@ editor = { path = "../editor" }
fs = { path = "../fs" }
gpui = { path = "../gpui" }
language = { path = "../language" }
+menu = { path = "../menu" }
search = { path = "../search" }
settings = { path = "../settings" }
theme = { path = "../theme" }
@@ -40,6 +40,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(AssistantEditor::assist);
cx.capture_action(AssistantEditor::cancel_last_assist);
cx.add_action(AssistantEditor::quote_selection);
+ cx.add_action(AssistantPanel::save_api_key);
}
pub enum AssistantPanelEvent {
@@ -54,6 +55,7 @@ pub struct AssistantPanel {
width: Option<f32>,
height: Option<f32>,
pane: ViewHandle<Pane>,
+ api_key_editor: ViewHandle<Editor>,
languages: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>,
_subscriptions: Vec<Subscription>,
@@ -124,6 +126,17 @@ impl AssistantPanel {
});
let mut this = Self {
pane,
+ api_key_editor: cx.add_view(|cx| {
+ let mut editor = Editor::single_line(
+ Some(Arc::new(|theme| theme.assistant.api_key_editor.clone())),
+ cx,
+ );
+ editor.set_placeholder_text(
+ "sk-000000000000000000000000000000000000000000000000",
+ cx,
+ );
+ editor
+ }),
languages: workspace.app_state().languages.clone(),
fs: workspace.app_state().fs.clone(),
width: None,
@@ -150,6 +163,9 @@ impl AssistantPanel {
.clone();
if old_openai_api_key != new_openai_api_key {
old_openai_api_key = new_openai_api_key;
+ if this.has_focus(cx) {
+ cx.focus_self();
+ }
cx.notify();
}
}),
@@ -183,6 +199,17 @@ impl AssistantPanel {
pane.add_item(Box::new(editor), true, focus, None, cx)
});
}
+
+ fn save_api_key(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+ let api_key = self.api_key_editor.read(cx).text(cx);
+ if !api_key.is_empty() {
+ settings::update_settings_file::<AssistantSettings>(
+ self.fs.clone(),
+ cx,
+ move |settings| settings.openai_api_key = Some(api_key),
+ );
+ }
+ }
}
impl Entity for AssistantPanel {
@@ -195,12 +222,44 @@ impl View for AssistantPanel {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- ChildView::new(&self.pane, cx).into_any()
+ let style = &theme::current(cx).assistant;
+ if settings::get::<AssistantSettings>(cx)
+ .openai_api_key
+ .is_none()
+ {
+ Flex::column()
+ .with_child(
+ Text::new(
+ "Paste your OpenAI API key and press Enter to use the assistant",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
+ .with_child(
+ ChildView::new(&self.api_key_editor, cx)
+ .contained()
+ .with_style(style.api_key_editor.container)
+ .aligned(),
+ )
+ .contained()
+ .with_style(style.api_key_prompt.container)
+ .aligned()
+ .into_any()
+ } else {
+ ChildView::new(&self.pane, cx).into_any()
+ }
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
- cx.focus(&self.pane);
+ if settings::get::<AssistantSettings>(cx)
+ .openai_api_key
+ .is_some()
+ {
+ cx.focus(&self.pane);
+ } else {
+ cx.focus(&self.api_key_editor);
+ }
}
}
}
@@ -290,7 +349,7 @@ impl Panel for AssistantPanel {
}
fn has_focus(&self, cx: &WindowContext) -> bool {
- self.pane.read(cx).has_focus()
+ self.pane.read(cx).has_focus() || self.api_key_editor.is_focused(cx)
}
fn is_focus_event(event: &Self::Event) -> bool {
@@ -976,6 +976,8 @@ pub struct AssistantStyle {
pub sent_at: ContainedText,
pub user_sender: ContainedText,
pub assistant_sender: ContainedText,
+ pub api_key_editor: FieldEditor,
+ pub api_key_prompt: ContainedText,
}
#[derive(Clone, Deserialize, Default)]
@@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
-import { text, border } from "./components"
+import { text, border, background } from "./components"
import editor from "./editor"
export default function assistant(colorScheme: ColorScheme) {
@@ -22,6 +22,26 @@ export default function assistant(colorScheme: ColorScheme) {
sent_at: {
margin: { top: 2, left: 8 },
...text(layer, "sans", "default", { size: "2xs" }),
+ },
+ apiKeyEditor: {
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ text: text(layer, "mono", "on"),
+ placeholderText: text(layer, "mono", "on", "disabled", {
+ size: "xs",
+ }),
+ selection: colorScheme.players[0],
+ border: border(layer, "on"),
+ padding: {
+ bottom: 4,
+ left: 8,
+ right: 8,
+ top: 4,
+ },
+ },
+ apiKeyPrompt: {
+ padding: 10,
+ ...text(layer, "sans", "default", { size: "xs" }),
}
}
}