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">
<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>
<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>

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,
AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
TextStyleRefinement, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
WindowContext,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use indexed_docs::IndexedDocsStore;
use language::{
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role};
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
use project::{Project, ProjectLspAdapterDelegate};
@ -59,7 +57,6 @@ use std::{
time::Duration,
};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use theme::ThemeSettings;
use ui::TintColor;
use ui::{
prelude::*,
@ -3035,7 +3032,6 @@ impl Item for ContextHistory {
pub struct ConfigurationView {
fallback_handle: FocusHandle,
using_assistant_description: View<Markdown>,
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 {
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)
});
fn new(fallback_handle: FocusHandle, _cx: &mut ViewContext<Self>) -> Self {
Self {
fallback_handle,
using_assistant_description: usage_description,
active_tab: None,
}
}
@ -3240,24 +3200,21 @@ impl Render for ConfigurationView {
.child(
v_flex()
.gap_2()
.child(
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(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)),
)
.child(
v_flex()
.gap_2()
.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)
.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;
use anyhow::Result;
use client::Client;
use client::{Client, UserStore};
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::*;
use project::Fs;
pub(crate) use rate_limiter::*;
@ -20,9 +22,14 @@ use schemars::JsonSchema;
use serde::de::DeserializeOwned;
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);
registry::init(client, cx);
registry::init(user_store, client, cx);
}
pub trait LanguageModel: Send + Sync {

View file

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

View file

@ -5,10 +5,12 @@ use crate::{
LanguageModelProviderState, LanguageModelRequest, RateLimiter,
};
use anyhow::{anyhow, Context as _, Result};
use client::Client;
use client::{Client, UserStore};
use collections::BTreeMap;
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 serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
@ -52,6 +54,7 @@ pub struct CloudLanguageModelProvider {
pub struct State {
client: Arc<Client>,
user_store: Model<UserStore>,
status: client::Status,
_subscription: Subscription,
}
@ -71,12 +74,13 @@ impl State {
}
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 status = *status_rx.borrow();
let state = cx.new_model(|cx| State {
client: client.clone(),
user_store,
status,
_subscription: cx.observe_global::<SettingsStore>(|_, cx| {
cx.notify();
@ -401,8 +405,9 @@ impl Render for ConfigurationView {
const ACCOUNT_SETTINGS_URL: &str = "https://zed.dev/settings";
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 {
v_flex()
@ -410,7 +415,7 @@ impl Render for ConfigurationView {
.max_w_4_5()
.child(Label::new(
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 {
"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()
.on_action(cx.listener(Self::save_api_key))
.children(
INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)),
INSTRUCTIONS.map(|instruction| Label::new(instruction)),
)
.child(
h_flex()

View file

@ -7,7 +7,7 @@ use ollama::{
};
use settings::{Settings, SettingsStore};
use std::{future, sync::Arc, time::Duration};
use ui::{prelude::*, ButtonLike, ElevationIndex, Indicator};
use ui::{prelude::*, ButtonLike, Indicator};
use crate::{
settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName,
@ -17,6 +17,7 @@ use crate::{
const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download";
const OLLAMA_LIBRARY_URL: &str = "https://ollama.com/library";
const OLLAMA_SITE: &str = "https://ollama.com/";
const PROVIDER_ID: &str = "ollama";
const PROVIDER_NAME: &str = "Ollama";
@ -303,81 +304,107 @@ impl ConfigurationView {
.update(cx, |state, cx| state.fetch_models(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 {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_authenticated = self.state.read(cx).is_authenticated();
if is_authenticated {
v_flex()
.size_full()
.child(
h_flex()
.gap_2()
.child(Indicator::dot().color(Color::Success))
.child(Label::new("Ollama configured").size(LabelSize::Small)),
)
.into_any()
} else {
v_flex()
let ollama_intro = "Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models with Ollama.";
let ollama_reqs =
"Ollama must be running with at least one model installed to use it in the assistant.";
let mut inline_code_bg = cx.theme().colors().editor_background;
inline_code_bg.fade_out(0.5);
v_flex()
.size_full()
.gap_2()
.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))
.gap_3()
.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(
h_flex()
.w_full()
.p_4()
.justify_center()
.pt_2()
.justify_between()
.gap_2()
.child(
self.render_download_button(cx)
)
.child(
self.render_retry_button(cx)
h_flex()
.w_full()
.gap_2()
.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()
}
}
}

View file

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