Allow saving the OpenAI API key in the assistant panel

This commit is contained in:
Antonio Scandurra 2023-06-02 11:38:02 +02:00
parent d0aff65b1c
commit a81d164ea6
6 changed files with 88 additions and 5 deletions

1
Cargo.lock generated
View file

@ -109,6 +109,7 @@ dependencies = [
"gpui", "gpui",
"isahc", "isahc",
"language", "language",
"menu",
"schemars", "schemars",
"search", "search",
"serde", "serde",

View file

@ -85,7 +85,7 @@
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'. // Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right", "dock": "right",
// Default width when the assistant is docked to the left or 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 when the assistant is docked to the bottom.
"default_height": 320, "default_height": 320,
// OpenAI API key. // OpenAI API key.

View file

@ -15,6 +15,7 @@ editor = { path = "../editor" }
fs = { path = "../fs" } fs = { path = "../fs" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
menu = { path = "../menu" }
search = { path = "../search" } search = { path = "../search" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }

View file

@ -40,6 +40,7 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(AssistantEditor::assist); cx.add_action(AssistantEditor::assist);
cx.capture_action(AssistantEditor::cancel_last_assist); cx.capture_action(AssistantEditor::cancel_last_assist);
cx.add_action(AssistantEditor::quote_selection); cx.add_action(AssistantEditor::quote_selection);
cx.add_action(AssistantPanel::save_api_key);
} }
pub enum AssistantPanelEvent { pub enum AssistantPanelEvent {
@ -54,6 +55,7 @@ pub struct AssistantPanel {
width: Option<f32>, width: Option<f32>,
height: Option<f32>, height: Option<f32>,
pane: ViewHandle<Pane>, pane: ViewHandle<Pane>,
api_key_editor: ViewHandle<Editor>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
@ -124,6 +126,17 @@ impl AssistantPanel {
}); });
let mut this = Self { let mut this = Self {
pane, 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(), languages: workspace.app_state().languages.clone(),
fs: workspace.app_state().fs.clone(), fs: workspace.app_state().fs.clone(),
width: None, width: None,
@ -150,6 +163,9 @@ impl AssistantPanel {
.clone(); .clone();
if old_openai_api_key != new_openai_api_key { if old_openai_api_key != new_openai_api_key {
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(); cx.notify();
} }
}), }),
@ -183,6 +199,17 @@ impl AssistantPanel {
pane.add_item(Box::new(editor), true, focus, None, cx) 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 { impl Entity for AssistantPanel {
@ -195,12 +222,44 @@ impl View for AssistantPanel {
} }
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { 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>) { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() { 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 { 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 { fn is_focus_event(event: &Self::Event) -> bool {

View file

@ -976,6 +976,8 @@ pub struct AssistantStyle {
pub sent_at: ContainedText, pub sent_at: ContainedText,
pub user_sender: ContainedText, pub user_sender: ContainedText,
pub assistant_sender: ContainedText, pub assistant_sender: ContainedText,
pub api_key_editor: FieldEditor,
pub api_key_prompt: ContainedText,
} }
#[derive(Clone, Deserialize, Default)] #[derive(Clone, Deserialize, Default)]

View file

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme" import { ColorScheme } from "../themes/common/colorScheme"
import { text, border } from "./components" import { text, border, background } from "./components"
import editor from "./editor" import editor from "./editor"
export default function assistant(colorScheme: ColorScheme) { export default function assistant(colorScheme: ColorScheme) {
@ -22,6 +22,26 @@ export default function assistant(colorScheme: ColorScheme) {
sent_at: { sent_at: {
margin: { top: 2, left: 8 }, margin: { top: 2, left: 8 },
...text(layer, "sans", "default", { size: "2xs" }), ...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" }),
} }
} }
} }