Update assistant config UI (#15630)

![CleanShot 2024-08-01 at 12 55
01@2x](https://github.com/user-attachments/assets/f9ed44ba-6bff-4805-ad71-2e3538315e57)

- Remove assisstant_description for now.
- Updates assistant config UI
- Updates Ollama and zed.dev provider UIs
- Updates download icon

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Nate Butler 2024-08-01 13:30:35 -04:00 committed by GitHub
parent 3bd9a3f478
commit 70b2da78f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 144 additions and 142 deletions

View file

@ -1,10 +1 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
<g clip-path="url(#clip0_430_1270)">
<path d="M4.30957 0.857736V4.28922L2.35703 4.28788C2.10067 4.28788 1.86816 4.44057 1.76636 4.67656C1.66511 4.91229 1.71332 5.18633 1.88959 5.37304L5.53269 9.23312C5.77565 9.49028 6.22488 9.49028 6.46784 9.23312L10.1123 5.37277C10.2875 5.18794 10.3354 4.9147 10.2342 4.6763C10.1337 4.44057 9.90066 4.28788 9.66761 4.28788H7.73891L7.73891 0.857736C7.73891 0.383865 7.35504 2.35669e-07 6.88171 2.14979e-07L5.16731 1.4004e-07C4.66906 -0.000267757 4.30957 0.383865 4.30957 0.857736ZM11.1433 11.1187C11.1434 10.6687 10.7595 10.2856 10.2861 10.2856H1.71413C1.23972 10.2856 0.856659 10.6687 0.856659 11.1187C0.856659 11.6169 1.23972 12 1.71386 12H10.2861C10.7595 12 11.1433 11.6169 11.1433 11.1187Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_430_1270">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 347 B

Before After
Before After

View file

@ -34,16 +34,14 @@ use gpui::{
div, percentage, point, svg, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext, div, percentage, point, svg, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter, AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
TextStyleRefinement, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView, UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
WindowContext,
}; };
use indexed_docs::IndexedDocsStore; use indexed_docs::IndexedDocsStore;
use language::{ use language::{
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset, language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
}; };
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role}; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role};
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{Project, ProjectLspAdapterDelegate}; use project::{Project, ProjectLspAdapterDelegate};
@ -59,7 +57,6 @@ use std::{
time::Duration, time::Duration,
}; };
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use theme::ThemeSettings;
use ui::TintColor; use ui::TintColor;
use ui::{ use ui::{
prelude::*, prelude::*,
@ -3035,7 +3032,6 @@ impl Item for ContextHistory {
pub struct ConfigurationView { pub struct ConfigurationView {
fallback_handle: FocusHandle, fallback_handle: FocusHandle,
using_assistant_description: View<Markdown>,
active_tab: Option<ActiveTab>, active_tab: Option<ActiveTab>,
} }
@ -3057,46 +3053,10 @@ impl ActiveTab {
} }
} }
// TODO: We need to remove this once we have proper text and styling
const SHOW_CONFIGURATION_TEXT: bool = false;
impl ConfigurationView { impl ConfigurationView {
fn new(fallback_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self { fn new(fallback_handle: FocusHandle, _cx: &mut ViewContext<Self>) -> Self {
let usage_description = cx.new_view(|cx| {
let text = include_str!("./using-the-assistant.md");
let settings = ThemeSettings::get_global(cx);
let mut base_text_style = cx.text_style();
base_text_style.refine(&TextStyleRefinement {
font_family: Some(settings.ui_font.family.clone()),
font_size: Some(TextSize::XSmall.rems(cx).into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(gpui::transparent_black()),
..Default::default()
});
let markdown_style = MarkdownStyle {
base_text_style,
selection_background_color: { cx.theme().players().local().selection },
inline_code: TextStyleRefinement {
background_color: Some(cx.theme().colors().background),
..Default::default()
},
link: TextStyleRefinement {
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(cx.theme().colors().editor_foreground),
wavy: false,
}),
..Default::default()
},
..Default::default()
};
Markdown::new(text.to_string(), markdown_style.clone(), None, cx, None)
});
Self { Self {
fallback_handle, fallback_handle,
using_assistant_description: usage_description,
active_tab: None, active_tab: None,
} }
} }
@ -3240,24 +3200,21 @@ impl Render for ConfigurationView {
.child( .child(
v_flex() v_flex()
.gap_2() .gap_2()
.child( .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)),
Headline::new("Get Started with the Assistant").size(HeadlineSize::Medium),
)
.child(
Label::new("Configure a provider to get started with the assistant. Then create a new context.")
.color(Color::Muted),
),
) )
.child( .child(
v_flex() v_flex()
.gap_2() .gap_2()
.child(Headline::new("Configure providers").size(HeadlineSize::Small)) .child(Headline::new("Configure providers").size(HeadlineSize::Small))
.child(
Label::new(
"At least one provider must be configured to use the assistant.",
)
.color(Color::Muted),
)
.child(tabs) .child(tabs)
.children(self.render_active_tab_view(cx)), .children(self.render_active_tab_view(cx)),
) )
.when(SHOW_CONFIGURATION_TEXT, |this| {
this.child(self.using_assistant_description.clone())
})
} }
} }

View file

@ -7,9 +7,11 @@ mod role;
pub mod settings; pub mod settings;
use anyhow::Result; use anyhow::Result;
use client::Client; use client::{Client, UserStore};
use futures::{future::BoxFuture, stream::BoxStream}; use futures::{future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, AppContext, AsyncAppContext, FocusHandle, SharedString, Task, WindowContext}; use gpui::{
AnyView, AppContext, AsyncAppContext, FocusHandle, Model, SharedString, Task, WindowContext,
};
pub use model::*; pub use model::*;
use project::Fs; use project::Fs;
pub(crate) use rate_limiter::*; pub(crate) use rate_limiter::*;
@ -20,9 +22,14 @@ use schemars::JsonSchema;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::{future::Future, sync::Arc}; use std::{future::Future, sync::Arc};
pub fn init(client: Arc<Client>, fs: Arc<dyn Fs>, cx: &mut AppContext) { pub fn init(
user_store: Model<UserStore>,
client: Arc<Client>,
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) {
settings::init(fs, cx); settings::init(fs, cx);
registry::init(client, cx); registry::init(user_store, client, cx);
} }
pub trait LanguageModel: Send + Sync { pub trait LanguageModel: Send + Sync {

View file

@ -483,7 +483,7 @@ impl Render for ConfigurationView {
.size_full() .size_full()
.on_action(cx.listener(Self::save_api_key)) .on_action(cx.listener(Self::save_api_key))
.children( .children(
INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)), INSTRUCTIONS.map(|instruction| Label::new(instruction)),
) )
.child( .child(
h_flex() h_flex()

View file

@ -5,10 +5,12 @@ use crate::{
LanguageModelProviderState, LanguageModelRequest, RateLimiter, LanguageModelProviderState, LanguageModelRequest, RateLimiter,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use client::Client; use client::{Client, UserStore};
use collections::BTreeMap; use collections::BTreeMap;
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, AsyncAppContext, FocusHandle, ModelContext, Subscription, Task}; use gpui::{
AnyView, AppContext, AsyncAppContext, FocusHandle, Model, ModelContext, Subscription, Task,
};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
@ -52,6 +54,7 @@ pub struct CloudLanguageModelProvider {
pub struct State { pub struct State {
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>,
status: client::Status, status: client::Status,
_subscription: Subscription, _subscription: Subscription,
} }
@ -71,12 +74,13 @@ impl State {
} }
impl CloudLanguageModelProvider { impl CloudLanguageModelProvider {
pub fn new(client: Arc<Client>, cx: &mut AppContext) -> Self { pub fn new(user_store: Model<UserStore>, client: Arc<Client>, cx: &mut AppContext) -> Self {
let mut status_rx = client.status(); let mut status_rx = client.status();
let status = *status_rx.borrow(); let status = *status_rx.borrow();
let state = cx.new_model(|cx| State { let state = cx.new_model(|cx| State {
client: client.clone(), client: client.clone(),
user_store,
status, status,
_subscription: cx.observe_global::<SettingsStore>(|_, cx| { _subscription: cx.observe_global::<SettingsStore>(|_, cx| {
cx.notify(); cx.notify();
@ -401,8 +405,9 @@ impl Render for ConfigurationView {
const ACCOUNT_SETTINGS_URL: &str = "https://zed.dev/settings"; const ACCOUNT_SETTINGS_URL: &str = "https://zed.dev/settings";
let is_connected = self.state.read(cx).is_connected(); let is_connected = self.state.read(cx).is_connected();
let plan = self.state.read(cx).user_store.read(cx).current_plan();
let is_pro = false; let is_pro = plan == Some(proto::Plan::ZedPro);
if is_connected { if is_connected {
v_flex() v_flex()
@ -410,7 +415,7 @@ impl Render for ConfigurationView {
.max_w_4_5() .max_w_4_5()
.child(Label::new( .child(Label::new(
if is_pro { if is_pro {
"You have full access to Zed's hosted models from Anthropic, OpenAI, Google through Zed Pro." "You have full access to Zed's hosted models from Anthropic, OpenAI, Google with faster speeds and higher limits through Zed Pro."
} else { } else {
"You have basic access to models from Anthropic, OpenAI, Google and more through the Zed AI Free plan." "You have basic access to models from Anthropic, OpenAI, Google and more through the Zed AI Free plan."
})) }))

View file

@ -398,7 +398,7 @@ impl Render for ConfigurationView {
.size_full() .size_full()
.on_action(cx.listener(Self::save_api_key)) .on_action(cx.listener(Self::save_api_key))
.children( .children(
INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)), INSTRUCTIONS.map(|instruction| Label::new(instruction)),
) )
.child( .child(
h_flex() h_flex()

View file

@ -7,7 +7,7 @@ use ollama::{
}; };
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use std::{future, sync::Arc, time::Duration}; use std::{future, sync::Arc, time::Duration};
use ui::{prelude::*, ButtonLike, ElevationIndex, Indicator}; use ui::{prelude::*, ButtonLike, Indicator};
use crate::{ use crate::{
settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName, settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName,
@ -17,6 +17,7 @@ use crate::{
const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download"; const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download";
const OLLAMA_LIBRARY_URL: &str = "https://ollama.com/library"; const OLLAMA_LIBRARY_URL: &str = "https://ollama.com/library";
const OLLAMA_SITE: &str = "https://ollama.com/";
const PROVIDER_ID: &str = "ollama"; const PROVIDER_ID: &str = "ollama";
const PROVIDER_NAME: &str = "Ollama"; const PROVIDER_NAME: &str = "Ollama";
@ -303,81 +304,107 @@ impl ConfigurationView {
.update(cx, |state, cx| state.fetch_models(cx)) .update(cx, |state, cx| state.fetch_models(cx))
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
fn render_download_button(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
ButtonLike::new("download_ollama_button")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Get Ollama"))
.on_click(move |_, cx| cx.open_url(OLLAMA_DOWNLOAD_URL))
}
fn render_retry_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
ButtonLike::new("retry_ollama_models")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Retry"))
.on_click(cx.listener(move |this, _, cx| this.retry_connection(cx)))
}
fn render_next_steps(&self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.p_4()
.size_full()
.gap_2()
.child(
Label::new("Once Ollama is on your machine, make sure to download a model or two.")
.size(LabelSize::Large),
)
.child(
h_flex().w_full().p_4().justify_center().gap_2().child(
ButtonLike::new("view-models")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("View Available Models"))
.on_click(move |_, cx| cx.open_url(OLLAMA_LIBRARY_URL)),
),
)
}
} }
impl Render for ConfigurationView { impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_authenticated = self.state.read(cx).is_authenticated(); let is_authenticated = self.state.read(cx).is_authenticated();
if is_authenticated { let ollama_intro = "Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models with Ollama.";
v_flex() let ollama_reqs =
.size_full() "Ollama must be running with at least one model installed to use it in the assistant.";
.child(
h_flex() let mut inline_code_bg = cx.theme().colors().editor_background;
.gap_2() inline_code_bg.fade_out(0.5);
.child(Indicator::dot().color(Color::Success))
.child(Label::new("Ollama configured").size(LabelSize::Small)), v_flex()
)
.into_any()
} else {
v_flex()
.size_full() .size_full()
.gap_2() .gap_3()
.child(Label::new("To use Ollama models via the assistant, Ollama must be running on your machine with at least one model downloaded.").size(LabelSize::Large)) .child(
v_flex()
.size_full()
.gap_2()
.p_1()
.child(Label::new(ollama_intro))
.child(Label::new(ollama_reqs))
.child(
h_flex()
.gap_0p5()
.child(Label::new("Once installed, try "))
.child(
div()
.bg(inline_code_bg)
.px_1p5()
.rounded_md()
.child(Label::new("ollama run llama3.1")),
),
),
)
.child( .child(
h_flex() h_flex()
.w_full() .w_full()
.p_4() .pt_2()
.justify_center() .justify_between()
.gap_2() .gap_2()
.child( .child(
self.render_download_button(cx) h_flex()
) .w_full()
.child( .gap_2()
self.render_retry_button(cx) .map(|this| {
if is_authenticated {
this.child(
Button::new("ollama-site", "Ollama")
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_SITE))
.into_any_element(),
)
} else {
this.child(
Button::new("download_ollama_button", "Download Ollama")
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_DOWNLOAD_URL))
.into_any_element(),
)
}
})
.child(
Button::new("view-models", "All Models")
.style(ButtonStyle::Subtle)
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_LIBRARY_URL)),
),
) )
.child(if is_authenticated {
// This is only a button to ensure the spacing is correct
// it should stay disabled
ButtonLike::new("connected")
.disabled(true)
// Since this won't ever be clickable, we can use the arrow cursor
.cursor_style(gpui::CursorStyle::Arrow)
.child(
h_flex()
.gap_2()
.child(Indicator::dot().color(Color::Success))
.child(Label::new("Connected"))
.into_any_element(),
)
.into_any_element()
} else {
Button::new("retry_ollama_models", "Connect")
.icon_position(IconPosition::Start)
.icon(IconName::ArrowCircle)
.on_click(cx.listener(move |this, _, cx| this.retry_connection(cx)))
.into_any_element()
}),
) )
.child(self.render_next_steps(cx))
.into_any() .into_any()
}
} }
} }

View file

@ -7,16 +7,16 @@ use crate::{
LanguageModel, LanguageModelId, LanguageModelProvider, LanguageModelProviderId, LanguageModel, LanguageModelId, LanguageModelProvider, LanguageModelProviderId,
LanguageModelProviderState, LanguageModelProviderState,
}; };
use client::Client; use client::{Client, UserStore};
use collections::BTreeMap; use collections::BTreeMap;
use gpui::{AppContext, EventEmitter, Global, Model, ModelContext}; use gpui::{AppContext, EventEmitter, Global, Model, ModelContext};
use std::sync::Arc; use std::sync::Arc;
use ui::Context; use ui::Context;
pub fn init(client: Arc<Client>, cx: &mut AppContext) { pub fn init(user_store: Model<UserStore>, client: Arc<Client>, cx: &mut AppContext) {
let registry = cx.new_model(|cx| { let registry = cx.new_model(|cx| {
let mut registry = LanguageModelRegistry::default(); let mut registry = LanguageModelRegistry::default();
register_language_model_providers(&mut registry, client, cx); register_language_model_providers(&mut registry, user_store, client, cx);
registry registry
}); });
cx.set_global(GlobalLanguageModelRegistry(registry)); cx.set_global(GlobalLanguageModelRegistry(registry));
@ -24,6 +24,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
fn register_language_model_providers( fn register_language_model_providers(
registry: &mut LanguageModelRegistry, registry: &mut LanguageModelRegistry,
user_store: Model<UserStore>,
client: Arc<Client>, client: Arc<Client>,
cx: &mut ModelContext<LanguageModelRegistry>, cx: &mut ModelContext<LanguageModelRegistry>,
) { ) {
@ -48,10 +49,14 @@ fn register_language_model_providers(
registry.register_provider(CopilotChatLanguageModelProvider::new(cx), cx); registry.register_provider(CopilotChatLanguageModelProvider::new(cx), cx);
cx.observe_flag::<feature_flags::LanguageModels, _>(move |enabled, cx| { cx.observe_flag::<feature_flags::LanguageModels, _>(move |enabled, cx| {
let user_store = user_store.clone();
let client = client.clone(); let client = client.clone();
LanguageModelRegistry::global(cx).update(cx, move |registry, cx| { LanguageModelRegistry::global(cx).update(cx, move |registry, cx| {
if enabled { if enabled {
registry.register_provider(CloudLanguageModelProvider::new(client.clone(), cx), cx); registry.register_provider(
CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx),
cx,
);
} else { } else {
registry.unregister_provider( registry.unregister_provider(
LanguageModelProviderId::from(crate::provider::cloud::PROVIDER_ID.to_string()), LanguageModelProviderId::from(crate::provider::cloud::PROVIDER_ID.to_string()),

View file

@ -174,7 +174,12 @@ fn init_common(app_state: Arc<AppState>, cx: &mut AppContext) {
cx, cx,
); );
supermaven::init(app_state.client.clone(), cx); supermaven::init(app_state.client.clone(), cx);
language_model::init(app_state.client.clone(), app_state.fs.clone(), cx); language_model::init(
app_state.user_store.clone(),
app_state.client.clone(),
app_state.fs.clone(),
cx,
);
snippet_provider::init(cx); snippet_provider::init(cx);
inline_completion_registry::init(app_state.client.telemetry().clone(), cx); inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx); assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);

View file

@ -3467,7 +3467,12 @@ mod tests {
app_state.client.http_client().clone(), app_state.client.http_client().clone(),
cx, cx,
); );
language_model::init(app_state.client.clone(), app_state.fs.clone(), cx); language_model::init(
app_state.user_store.clone(),
app_state.client.clone(),
app_state.fs.clone(),
cx,
);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx); assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
repl::init( repl::init(
app_state.fs.clone(), app_state.fs.clone(),