diff --git a/assets/icons/download.svg b/assets/icons/download.svg index bcf66dfdf7..2ffa65e8ac 100644 --- a/assets/icons/download.svg +++ b/assets/icons/download.svg @@ -1,10 +1 @@ - - - - - - - - - - + diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index bae65d7731..064279bb4b 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -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, active_tab: Option, } @@ -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 { - 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 { 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()) - }) } } diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index 98abbcb56c..e92cbd652b 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -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, fs: Arc, cx: &mut AppContext) { +pub fn init( + user_store: Model, + client: Arc, + fs: Arc, + cx: &mut AppContext, +) { settings::init(fs, cx); - registry::init(client, cx); + registry::init(user_store, client, cx); } pub trait LanguageModel: Send + Sync { diff --git a/crates/language_model/src/provider/anthropic.rs b/crates/language_model/src/provider/anthropic.rs index d92fddd493..3b96035ffb 100644 --- a/crates/language_model/src/provider/anthropic.rs +++ b/crates/language_model/src/provider/anthropic.rs @@ -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() diff --git a/crates/language_model/src/provider/cloud.rs b/crates/language_model/src/provider/cloud.rs index 4b8018ba46..2c5ab1ca5e 100644 --- a/crates/language_model/src/provider/cloud.rs +++ b/crates/language_model/src/provider/cloud.rs @@ -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, + user_store: Model, status: client::Status, _subscription: Subscription, } @@ -71,12 +74,13 @@ impl State { } impl CloudLanguageModelProvider { - pub fn new(client: Arc, cx: &mut AppContext) -> Self { + pub fn new(user_store: Model, client: Arc, 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::(|_, 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." })) diff --git a/crates/language_model/src/provider/google.rs b/crates/language_model/src/provider/google.rs index 368778ce29..0547d7e98c 100644 --- a/crates/language_model/src/provider/google.rs +++ b/crates/language_model/src/provider/google.rs @@ -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() diff --git a/crates/language_model/src/provider/ollama.rs b/crates/language_model/src/provider/ollama.rs index 934b87eb89..c2aace0ba1 100644 --- a/crates/language_model/src/provider/ollama.rs +++ b/crates/language_model/src/provider/ollama.rs @@ -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) -> 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) -> 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) -> 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) -> 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() - } } } diff --git a/crates/language_model/src/registry.rs b/crates/language_model/src/registry.rs index 94a69a9d2f..067573bc38 100644 --- a/crates/language_model/src/registry.rs +++ b/crates/language_model/src/registry.rs @@ -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, cx: &mut AppContext) { +pub fn init(user_store: Model, client: Arc, 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, cx: &mut AppContext) { fn register_language_model_providers( registry: &mut LanguageModelRegistry, + user_store: Model, client: Arc, cx: &mut ModelContext, ) { @@ -48,10 +49,14 @@ fn register_language_model_providers( registry.register_provider(CopilotChatLanguageModelProvider::new(cx), cx); cx.observe_flag::(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()), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a6c4ea02fb..147781b37b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -174,7 +174,12 @@ fn init_common(app_state: Arc, 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); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6d7b47a158..ccc989f0c7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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(),