Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use client::{Client, UserStore};
use fs::Fs;
use gpui::{AppContext, Model, ModelContext};
use gpui::{App, Context, Entity};
use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
mod logging;
@ -21,12 +21,7 @@ use crate::provider::open_ai::OpenAiLanguageModelProvider;
pub use crate::settings::*;
pub use logging::report_assistant_event;
pub fn init(
user_store: Model<UserStore>,
client: Arc<Client>,
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) {
pub fn init(user_store: Entity<UserStore>, client: Arc<Client>, fs: Arc<dyn Fs>, cx: &mut App) {
crate::settings::init(fs, cx);
let registry = LanguageModelRegistry::global(cx);
registry.update(cx, |registry, cx| {
@ -36,9 +31,9 @@ pub fn init(
fn register_language_model_providers(
registry: &mut LanguageModelRegistry,
user_store: Model<UserStore>,
user_store: Entity<UserStore>,
client: Arc<Client>,
cx: &mut ModelContext<LanguageModelRegistry>,
cx: &mut Context<LanguageModelRegistry>,
) {
use feature_flags::FeatureFlagAppExt;

View file

@ -1,5 +1,5 @@
use anthropic::{AnthropicError, ANTHROPIC_API_URL};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Context as _, Result};
use client::telemetry::Telemetry;
use gpui::BackgroundExecutor;
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};

View file

@ -6,8 +6,8 @@ use editor::{Editor, EditorElement, EditorStyle};
use futures::Stream;
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt, TryStreamExt as _};
use gpui::{
AnyView, AppContext, AsyncAppContext, FontStyle, ModelContext, Subscription, Task, TextStyle,
View, WhiteSpace,
AnyView, App, AsyncAppContext, Context, Entity, FontStyle, Subscription, Task, TextStyle,
WhiteSpace,
};
use http_client::HttpClient;
use language_model::{
@ -58,7 +58,7 @@ pub struct AvailableModel {
pub struct AnthropicLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
const ANTHROPIC_API_KEY_VAR: &str = "ANTHROPIC_API_KEY";
@ -70,7 +70,7 @@ pub struct State {
}
impl State {
fn reset_api_key(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let delete_credentials =
cx.delete_credentials(&AllLanguageModelSettings::get_global(cx).anthropic.api_url);
cx.spawn(|this, mut cx| async move {
@ -83,7 +83,7 @@ impl State {
})
}
fn set_api_key(&mut self, api_key: String, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {
let write_credentials = cx.write_credentials(
AllLanguageModelSettings::get_global(cx)
.anthropic
@ -106,7 +106,7 @@ impl State {
self.api_key.is_some()
}
fn authenticate(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@ -138,8 +138,8 @@ impl State {
}
impl AnthropicLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let state = cx.new(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_, cx| {
@ -154,7 +154,7 @@ impl AnthropicLanguageModelProvider {
impl LanguageModelProviderState for AnthropicLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -172,7 +172,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
IconName::AiAnthropic
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
// Add base models from anthropic::Model::iter()
@ -223,20 +223,20 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
.collect()
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|cx| ConfigurationView::new(self.state.clone(), cx))
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
.into()
}
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.reset_api_key(cx))
}
}
@ -244,14 +244,14 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
pub struct AnthropicModel {
id: LanguageModelId,
model: anthropic::Model,
state: gpui::Model<State>,
state: gpui::Entity<State>,
http_client: Arc<dyn HttpClient>,
request_limiter: RateLimiter,
}
pub fn count_anthropic_tokens(
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
cx.background_executor()
.spawn(async move {
@ -350,7 +350,7 @@ impl LanguageModel for AnthropicModel {
format!("anthropic/{}", self.model.id())
}
fn api_key(&self, cx: &AppContext) -> Option<String> {
fn api_key(&self, cx: &App) -> Option<String> {
self.state.read(cx).api_key.clone()
}
@ -365,7 +365,7 @@ impl LanguageModel for AnthropicModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
count_anthropic_tokens(request, cx)
}
@ -562,15 +562,15 @@ pub fn map_to_language_model_completion_events(
}
struct ConfigurationView {
api_key_editor: View<Editor>,
state: gpui::Model<State>,
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,
load_credentials_task: Option<Task<()>>,
}
impl ConfigurationView {
const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
fn new(state: gpui::Model<State>, cx: &mut ViewContext<Self>) -> Self {
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
cx.observe(&state, |_, _, cx| {
cx.notify();
})
@ -595,8 +595,8 @@ impl ConfigurationView {
}));
Self {
api_key_editor: cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
api_key_editor: cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_placeholder_text(Self::PLACEHOLDER_TEXT, cx);
editor
}),
@ -605,14 +605,14 @@ impl ConfigurationView {
}
}
fn save_api_key(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let api_key = self.api_key_editor.read(cx).text(cx);
if api_key.is_empty() {
return;
}
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.set_api_key(api_key, cx))?
.await
@ -622,12 +622,12 @@ impl ConfigurationView {
cx.notify();
}
fn reset_api_key(&mut self, cx: &mut ViewContext<Self>) {
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", cx));
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.reset_api_key(cx))?
.await
@ -637,7 +637,7 @@ impl ConfigurationView {
cx.notify();
}
fn render_api_key_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
@ -665,13 +665,13 @@ impl ConfigurationView {
)
}
fn should_render_editor(&self, cx: &mut ViewContext<Self>) -> bool {
fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
!self.state.read(cx).is_authenticated()
}
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const ANTHROPIC_CONSOLE_URL: &str = "https://console.anthropic.com/settings/keys";
const INSTRUCTIONS: [&str; 3] = [
"To use Zed's assistant with Anthropic, you need to add an API key. Follow these steps:",
@ -693,7 +693,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
.on_click(move |_, _, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
)
)
.child(Label::new(INSTRUCTIONS[2]))
@ -735,9 +735,9 @@ impl Render for ConfigurationView {
.icon_position(IconPosition::Start)
.disabled(env_var_set)
.when(env_var_set, |this| {
this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {ANTHROPIC_API_KEY_VAR} environment variable."), cx))
this.tooltip(Tooltip::text(format!("To reset your API key, unset the {ANTHROPIC_API_KEY_VAR} environment variable.")))
})
.on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))),
.on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))),
)
.into_any()
}

View file

@ -12,8 +12,8 @@ use futures::{
TryStreamExt as _,
};
use gpui::{
AnyElement, AnyView, AppContext, AsyncAppContext, EventEmitter, Global, Model, ModelContext,
ReadGlobal, Subscription, Task,
AnyElement, AnyView, App, AsyncAppContext, Context, Entity, EventEmitter, Global, ReadGlobal,
Subscription, Task,
};
use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode};
use language_model::{
@ -99,7 +99,7 @@ pub struct AvailableModel {
pub extra_beta_headers: Vec<String>,
}
struct GlobalRefreshLlmTokenListener(Model<RefreshLlmTokenListener>);
struct GlobalRefreshLlmTokenListener(Entity<RefreshLlmTokenListener>);
impl Global for GlobalRefreshLlmTokenListener {}
@ -112,16 +112,16 @@ pub struct RefreshLlmTokenListener {
impl EventEmitter<RefreshLlmTokenEvent> for RefreshLlmTokenListener {}
impl RefreshLlmTokenListener {
pub fn register(client: Arc<Client>, cx: &mut AppContext) {
let listener = cx.new_model(|cx| RefreshLlmTokenListener::new(client, cx));
pub fn register(client: Arc<Client>, cx: &mut App) {
let listener = cx.new(|cx| RefreshLlmTokenListener::new(client, cx));
cx.set_global(GlobalRefreshLlmTokenListener(listener));
}
pub fn global(cx: &AppContext) -> Model<Self> {
pub fn global(cx: &App) -> Entity<Self> {
GlobalRefreshLlmTokenListener::global(cx).0.clone()
}
fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
Self {
_llm_token_subscription: client
.add_message_handler(cx.weak_model(), Self::handle_refresh_llm_token),
@ -129,7 +129,7 @@ impl RefreshLlmTokenListener {
}
async fn handle_refresh_llm_token(
this: Model<Self>,
this: Entity<Self>,
_: TypedEnvelope<proto::RefreshLlmToken>,
mut cx: AsyncAppContext,
) -> Result<()> {
@ -139,14 +139,14 @@ impl RefreshLlmTokenListener {
pub struct CloudLanguageModelProvider {
client: Arc<Client>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
_maintain_client_status: Task<()>,
}
pub struct State {
client: Arc<Client>,
llm_api_token: LlmApiToken,
user_store: Model<UserStore>,
user_store: Entity<UserStore>,
status: client::Status,
accept_terms: Option<Task<Result<()>>>,
_settings_subscription: Subscription,
@ -156,9 +156,9 @@ pub struct State {
impl State {
fn new(
client: Arc<Client>,
user_store: Model<UserStore>,
user_store: Entity<UserStore>,
status: client::Status,
cx: &mut ModelContext<Self>,
cx: &mut Context<Self>,
) -> Self {
let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx);
@ -190,7 +190,7 @@ impl State {
self.status.is_signed_out()
}
fn authenticate(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let client = self.client.clone();
cx.spawn(move |this, mut cx| async move {
client.authenticate_and_connect(true, &cx).await?;
@ -198,14 +198,14 @@ impl State {
})
}
fn has_accepted_terms_of_service(&self, cx: &AppContext) -> bool {
fn has_accepted_terms_of_service(&self, cx: &App) -> bool {
self.user_store
.read(cx)
.current_user_has_accepted_terms()
.unwrap_or(false)
}
fn accept_terms_of_service(&mut self, cx: &mut ModelContext<Self>) {
fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) {
let user_store = self.user_store.clone();
self.accept_terms = Some(cx.spawn(move |this, mut cx| async move {
let _ = user_store
@ -220,11 +220,11 @@ impl State {
}
impl CloudLanguageModelProvider {
pub fn new(user_store: Model<UserStore>, client: Arc<Client>, cx: &mut AppContext) -> Self {
pub fn new(user_store: Entity<UserStore>, client: Arc<Client>, cx: &mut App) -> Self {
let mut status_rx = client.status();
let status = *status_rx.borrow();
let state = cx.new_model(|cx| State::new(client.clone(), user_store.clone(), status, cx));
let state = cx.new(|cx| State::new(client.clone(), user_store.clone(), status, cx));
let state_ref = state.downgrade();
let maintain_client_status = cx.spawn(|mut cx| async move {
@ -253,7 +253,7 @@ impl CloudLanguageModelProvider {
impl LanguageModelProviderState for CloudLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -271,7 +271,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
IconName::AiZed
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
if cx.is_staff() {
@ -359,42 +359,42 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
.collect()
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
!self.state.read(cx).is_signed_out()
}
fn authenticate(&self, _cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|_cx| ConfigurationView {
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
cx.new(|_| ConfigurationView {
state: self.state.clone(),
})
.into()
}
fn must_accept_terms(&self, cx: &AppContext) -> bool {
fn must_accept_terms(&self, cx: &App) -> bool {
!self.state.read(cx).has_accepted_terms_of_service(cx)
}
fn render_accept_terms(
&self,
view: LanguageModelProviderTosView,
cx: &mut WindowContext,
cx: &mut App,
) -> Option<AnyElement> {
render_accept_terms(self.state.clone(), view, cx)
}
fn reset_credentials(&self, _cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Ok(()))
}
}
fn render_accept_terms(
state: Model<State>,
state: Entity<State>,
view_kind: LanguageModelProviderTosView,
cx: &mut WindowContext,
cx: &mut App,
) -> Option<AnyElement> {
if state.read(cx).has_accepted_terms_of_service(cx) {
return None;
@ -407,7 +407,7 @@ fn render_accept_terms(
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_size(IconSize::XSmall)
.on_click(move |_, cx| cx.open_url("https://zed.dev/terms-of-service"));
.on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service"));
let text = "To start using Zed AI, please read and accept the";
@ -435,7 +435,7 @@ fn render_accept_terms(
.disabled(accept_terms_disabled)
.on_click({
let state = state.downgrade();
move |_, cx| {
move |_, _window, cx| {
state
.update(cx, |state, cx| state.accept_terms_of_service(cx))
.ok();
@ -592,7 +592,7 @@ impl LanguageModel for CloudLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
match self.model.clone() {
CloudModel::Anthropic(_) => count_anthropic_tokens(request, cx),
@ -856,11 +856,11 @@ impl LlmApiToken {
}
struct ConfigurationView {
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
impl ConfigurationView {
fn authenticate(&mut self, cx: &mut ViewContext<Self>) {
fn authenticate(&mut self, cx: &mut Context<Self>) {
self.state.update(cx, |state, cx| {
state.authenticate(cx).detach_and_log_err(cx);
});
@ -869,7 +869,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const ZED_AI_URL: &str = "https://zed.dev/ai";
let is_connected = !self.state.read(cx).is_signed_out();
@ -887,7 +887,9 @@ impl Render for ConfigurationView {
h_flex().child(
Button::new("manage_settings", "Manage Subscription")
.style(ButtonStyle::Tinted(TintColor::Accent))
.on_click(cx.listener(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))),
.on_click(
cx.listener(|_, _, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
),
)
} else if cx.has_flag::<ZedPro>() {
@ -897,14 +899,14 @@ impl Render for ConfigurationView {
.child(
Button::new("learn_more", "Learn more")
.style(ButtonStyle::Subtle)
.on_click(cx.listener(|_, _, cx| cx.open_url(ZED_AI_URL))),
.on_click(cx.listener(|_, _, _, cx| cx.open_url(ZED_AI_URL))),
)
.child(
Button::new("upgrade", "Upgrade")
.style(ButtonStyle::Subtle)
.color(Color::Accent)
.on_click(
cx.listener(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
cx.listener(|_, _, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
),
)
@ -934,7 +936,7 @@ impl Render for ConfigurationView {
.icon_color(Color::Muted)
.icon(IconName::Github)
.icon_position(IconPosition::Start)
.on_click(cx.listener(move |this, _, cx| this.authenticate(cx))),
.on_click(cx.listener(move |this, _, _, cx| this.authenticate(cx))),
)
}
}

View file

@ -11,7 +11,7 @@ use futures::future::BoxFuture;
use futures::stream::BoxStream;
use futures::{FutureExt, StreamExt};
use gpui::{
percentage, svg, Animation, AnimationExt, AnyView, AppContext, AsyncAppContext, Model, Render,
percentage, svg, Animation, AnimationExt, AnyView, App, AsyncAppContext, Entity, Render,
Subscription, Task, Transformation,
};
use language_model::{
@ -34,7 +34,7 @@ const PROVIDER_NAME: &str = "GitHub Copilot Chat";
pub struct CopilotChatSettings {}
pub struct CopilotChatLanguageModelProvider {
state: Model<State>,
state: Entity<State>,
}
pub struct State {
@ -43,7 +43,7 @@ pub struct State {
}
impl State {
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
CopilotChat::global(cx)
.map(|m| m.read(cx).is_authenticated())
.unwrap_or(false)
@ -51,8 +51,8 @@ impl State {
}
impl CopilotChatLanguageModelProvider {
pub fn new(cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| {
pub fn new(cx: &mut App) -> Self {
let state = cx.new(|cx| {
let _copilot_chat_subscription = CopilotChat::global(cx)
.map(|copilot_chat| cx.observe(&copilot_chat, |_, _, cx| cx.notify()));
State {
@ -70,7 +70,7 @@ impl CopilotChatLanguageModelProvider {
impl LanguageModelProviderState for CopilotChatLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -88,7 +88,7 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
IconName::Copilot
}
fn provided_models(&self, _cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, _cx: &App) -> Vec<Arc<dyn LanguageModel>> {
CopilotChatModel::iter()
.map(|model| {
Arc::new(CopilotChatLanguageModel {
@ -99,11 +99,11 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
.collect()
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated(cx)
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
let result = if self.is_authenticated(cx) {
Ok(())
} else if let Some(copilot) = Copilot::global(cx) {
@ -125,12 +125,12 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider {
Task::ready(result)
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView {
let state = self.state.clone();
cx.new_view(|cx| ConfigurationView::new(state, cx)).into()
cx.new(|cx| ConfigurationView::new(state, cx)).into()
}
fn reset_credentials(&self, _cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!(
"Signing out of GitHub Copilot Chat is currently not supported."
)))
@ -170,7 +170,7 @@ impl LanguageModel for CopilotChatLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
match self.model {
CopilotChatModel::Claude3_5Sonnet => count_anthropic_tokens(request, cx),
@ -294,12 +294,12 @@ impl CopilotChatLanguageModel {
struct ConfigurationView {
copilot_status: Option<copilot::Status>,
state: Model<State>,
state: Entity<State>,
_subscription: Option<Subscription>,
}
impl ConfigurationView {
pub fn new(state: Model<State>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(state: Entity<State>, cx: &mut Context<Self>) -> Self {
let copilot = Copilot::global(cx);
Self {
@ -316,7 +316,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.state.read(cx).is_authenticated(cx) {
const LABEL: &str = "Authorized.";
h_flex()
@ -327,7 +327,7 @@ impl Render for ConfigurationView {
let loading_icon = svg()
.size_8()
.path(IconName::ArrowCircle.path())
.text_color(cx.text_style().color)
.text_color(window.text_style().color)
.with_animation(
"icon_circle_arrow",
Animation::new(Duration::from_secs(2)).repeat(),
@ -378,7 +378,9 @@ impl Render for ConfigurationView {
.icon_size(IconSize::Medium)
.style(ui::ButtonStyle::Filled)
.full_width()
.on_click(|_, cx| copilot::initiate_sign_in(cx)),
.on_click(|_, window, cx| {
copilot::initiate_sign_in(window, cx)
}),
)
.child(
div().flex().w_full().items_center().child(

View file

@ -4,8 +4,8 @@ use editor::{Editor, EditorElement, EditorStyle};
use futures::{future::BoxFuture, FutureExt, StreamExt};
use google_ai::stream_generate_content;
use gpui::{
AnyView, AppContext, AsyncAppContext, FontStyle, ModelContext, Subscription, Task, TextStyle,
View, WhiteSpace,
AnyView, App, AsyncAppContext, Context, Entity, FontStyle, Subscription, Task, TextStyle,
WhiteSpace,
};
use http_client::HttpClient;
use language_model::LanguageModelCompletionEvent;
@ -43,7 +43,7 @@ pub struct AvailableModel {
pub struct GoogleLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
pub struct State {
@ -59,7 +59,7 @@ impl State {
self.api_key.is_some()
}
fn reset_api_key(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let delete_credentials =
cx.delete_credentials(&AllLanguageModelSettings::get_global(cx).google.api_url);
cx.spawn(|this, mut cx| async move {
@ -72,7 +72,7 @@ impl State {
})
}
fn set_api_key(&mut self, api_key: String, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {
let settings = &AllLanguageModelSettings::get_global(cx).google;
let write_credentials =
cx.write_credentials(&settings.api_url, "Bearer", api_key.as_bytes());
@ -86,7 +86,7 @@ impl State {
})
}
fn authenticate(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@ -118,8 +118,8 @@ impl State {
}
impl GoogleLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let state = cx.new(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_, cx| {
@ -134,7 +134,7 @@ impl GoogleLanguageModelProvider {
impl LanguageModelProviderState for GoogleLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -152,7 +152,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
IconName::AiGoogle
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
// Add base models from google_ai::Model::iter()
@ -191,20 +191,20 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
.collect()
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|cx| ConfigurationView::new(self.state.clone(), cx))
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
.into()
}
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
let state = self.state.clone();
let delete_credentials =
cx.delete_credentials(&AllLanguageModelSettings::get_global(cx).google.api_url);
@ -221,7 +221,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
pub struct GoogleLanguageModel {
id: LanguageModelId,
model: google_ai::Model,
state: gpui::Model<State>,
state: gpui::Entity<State>,
http_client: Arc<dyn HttpClient>,
rate_limiter: RateLimiter,
}
@ -254,7 +254,7 @@ impl LanguageModel for GoogleLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
let request = request.into_google(self.model.id().to_string());
let http_client = self.http_client.clone();
@ -326,19 +326,19 @@ impl LanguageModel for GoogleLanguageModel {
}
struct ConfigurationView {
api_key_editor: View<Editor>,
state: gpui::Model<State>,
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,
load_credentials_task: Option<Task<()>>,
}
impl ConfigurationView {
fn new(state: gpui::Model<State>, cx: &mut ViewContext<Self>) -> Self {
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
cx.observe(&state, |_, _, cx| {
cx.notify();
})
.detach();
let load_credentials_task = Some(cx.spawn({
let load_credentials_task = Some(cx.spawn_in(window, {
let state = state.clone();
|this, mut cx| async move {
if let Some(task) = state
@ -357,8 +357,8 @@ impl ConfigurationView {
}));
Self {
api_key_editor: cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
api_key_editor: cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_placeholder_text("AIzaSy...", cx);
editor
}),
@ -367,14 +367,14 @@ impl ConfigurationView {
}
}
fn save_api_key(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let api_key = self.api_key_editor.read(cx).text(cx);
if api_key.is_empty() {
return;
}
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.set_api_key(api_key, cx))?
.await
@ -384,12 +384,12 @@ impl ConfigurationView {
cx.notify();
}
fn reset_api_key(&mut self, cx: &mut ViewContext<Self>) {
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", cx));
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.reset_api_key(cx))?
.await
@ -399,7 +399,7 @@ impl ConfigurationView {
cx.notify();
}
fn render_api_key_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
@ -427,13 +427,13 @@ impl ConfigurationView {
)
}
fn should_render_editor(&self, cx: &mut ViewContext<Self>) -> bool {
fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
!self.state.read(cx).is_authenticated()
}
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const GOOGLE_CONSOLE_URL: &str = "https://aistudio.google.com/app/apikey";
const INSTRUCTIONS: [&str; 3] = [
"To use Zed's assistant with Google AI, you need to add an API key. Follow these steps:",
@ -456,7 +456,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(GOOGLE_CONSOLE_URL))
.on_click(move |_, _, cx| cx.open_url(GOOGLE_CONSOLE_URL))
)
)
.child(Label::new(INSTRUCTIONS[2]))
@ -498,9 +498,9 @@ impl Render for ConfigurationView {
.icon_position(IconPosition::Start)
.disabled(env_var_set)
.when(env_var_set, |this| {
this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {GOOGLE_AI_API_KEY_VAR} environment variable."), cx))
this.tooltip(Tooltip::text(format!("To reset your API key, unset the {GOOGLE_AI_API_KEY_VAR} environment variable.")))
})
.on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))),
.on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))),
)
.into_any()
}

View file

@ -1,6 +1,6 @@
use anyhow::{anyhow, Result};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, AsyncAppContext, ModelContext, Subscription, Task};
use gpui::{AnyView, App, AsyncAppContext, Context, Subscription, Task};
use http_client::HttpClient;
use language_model::LanguageModelCompletionEvent;
use language_model::{
@ -46,7 +46,7 @@ pub struct AvailableModel {
pub struct LmStudioLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
pub struct State {
@ -61,7 +61,7 @@ impl State {
!self.available_models.is_empty()
}
fn fetch_models(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn fetch_models(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let settings = &AllLanguageModelSettings::get_global(cx).lmstudio;
let http_client = self.http_client.clone();
let api_url = settings.api_url.clone();
@ -85,12 +85,12 @@ impl State {
})
}
fn restart_fetch_models_task(&mut self, cx: &mut ModelContext<Self>) {
fn restart_fetch_models_task(&mut self, cx: &mut Context<Self>) {
let task = self.fetch_models(cx);
self.fetch_model_task.replace(task);
}
fn authenticate(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@ -100,10 +100,10 @@ impl State {
}
impl LmStudioLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let this = Self {
http_client: http_client.clone(),
state: cx.new_model(|cx| {
state: cx.new(|cx| {
let subscription = cx.observe_global::<SettingsStore>({
let mut settings = AllLanguageModelSettings::get_global(cx).lmstudio.clone();
move |this: &mut State, cx| {
@ -133,7 +133,7 @@ impl LmStudioLanguageModelProvider {
impl LanguageModelProviderState for LmStudioLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -151,7 +151,7 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
IconName::AiLmStudio
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models: BTreeMap<String, lmstudio::Model> = BTreeMap::default();
// Add models from the LM Studio API
@ -188,7 +188,7 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
.collect()
}
fn load_model(&self, model: Arc<dyn LanguageModel>, cx: &AppContext) {
fn load_model(&self, model: Arc<dyn LanguageModel>, cx: &App) {
let settings = &AllLanguageModelSettings::get_global(cx).lmstudio;
let http_client = self.http_client.clone();
let api_url = settings.api_url.clone();
@ -197,20 +197,20 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider {
.detach_and_log_err(cx);
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
fn configuration_view(&self, _window: &mut Window, cx: &mut App) -> AnyView {
let state = self.state.clone();
cx.new_view(|cx| ConfigurationView::new(state, cx)).into()
cx.new(|cx| ConfigurationView::new(state, cx)).into()
}
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.fetch_models(cx))
}
}
@ -279,7 +279,7 @@ impl LanguageModel for LmStudioLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
_cx: &AppContext,
_cx: &App,
) -> BoxFuture<'static, Result<usize>> {
// Endpoint for this is coming soon. In the meantime, hacky estimation
let token_count = request
@ -369,12 +369,12 @@ impl LanguageModel for LmStudioLanguageModel {
}
struct ConfigurationView {
state: gpui::Model<State>,
state: gpui::Entity<State>,
loading_models_task: Option<Task<()>>,
}
impl ConfigurationView {
pub fn new(state: gpui::Model<State>, cx: &mut ViewContext<Self>) -> Self {
pub fn new(state: gpui::Entity<State>, cx: &mut Context<Self>) -> Self {
let loading_models_task = Some(cx.spawn({
let state = state.clone();
|this, mut cx| async move {
@ -398,7 +398,7 @@ impl ConfigurationView {
}
}
fn retry_connection(&self, cx: &mut WindowContext) {
fn retry_connection(&self, cx: &mut App) {
self.state
.update(cx, |state, cx| state.fetch_models(cx))
.detach_and_log_err(cx);
@ -406,7 +406,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_authenticated = self.state.read(cx).is_authenticated();
let lmstudio_intro = "Run local LLMs like Llama, Phi, and Qwen.";
@ -460,7 +460,9 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(LMSTUDIO_SITE))
.on_click(move |_, _window, cx| {
cx.open_url(LMSTUDIO_SITE)
})
.into_any_element(),
)
} else {
@ -473,7 +475,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| {
.on_click(move |_, _window, cx| {
cx.open_url(LMSTUDIO_DOWNLOAD_URL)
})
.into_any_element(),
@ -486,7 +488,9 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(LMSTUDIO_CATALOG_URL)),
.on_click(move |_, _window, cx| {
cx.open_url(LMSTUDIO_CATALOG_URL)
}),
),
)
.child(if is_authenticated {
@ -508,7 +512,9 @@ impl Render for ConfigurationView {
Button::new("retry_lmstudio_models", "Connect")
.icon_position(IconPosition::Start)
.icon(IconName::ArrowCircle)
.on_click(cx.listener(move |this, _, cx| this.retry_connection(cx)))
.on_click(cx.listener(move |this, _, _window, cx| {
this.retry_connection(cx)
}))
.into_any_element()
}),
)

View file

@ -1,6 +1,6 @@
use anyhow::{anyhow, bail, Result};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, AsyncAppContext, ModelContext, Subscription, Task};
use gpui::{AnyView, App, AsyncAppContext, Context, Subscription, Task};
use http_client::HttpClient;
use language_model::LanguageModelCompletionEvent;
use language_model::{
@ -48,7 +48,7 @@ pub struct AvailableModel {
pub struct OllamaLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
pub struct State {
@ -63,7 +63,7 @@ impl State {
!self.available_models.is_empty()
}
fn fetch_models(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn fetch_models(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let settings = &AllLanguageModelSettings::get_global(cx).ollama;
let http_client = self.http_client.clone();
let api_url = settings.api_url.clone();
@ -90,12 +90,12 @@ impl State {
})
}
fn restart_fetch_models_task(&mut self, cx: &mut ModelContext<Self>) {
fn restart_fetch_models_task(&mut self, cx: &mut Context<Self>) {
let task = self.fetch_models(cx);
self.fetch_model_task.replace(task);
}
fn authenticate(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@ -105,10 +105,10 @@ impl State {
}
impl OllamaLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let this = Self {
http_client: http_client.clone(),
state: cx.new_model(|cx| {
state: cx.new(|cx| {
let subscription = cx.observe_global::<SettingsStore>({
let mut settings = AllLanguageModelSettings::get_global(cx).ollama.clone();
move |this: &mut State, cx| {
@ -138,7 +138,7 @@ impl OllamaLanguageModelProvider {
impl LanguageModelProviderState for OllamaLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -156,7 +156,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
IconName::AiOllama
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models: BTreeMap<String, ollama::Model> = BTreeMap::default();
// Add models from the Ollama API
@ -194,7 +194,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
.collect()
}
fn load_model(&self, model: Arc<dyn LanguageModel>, cx: &AppContext) {
fn load_model(&self, model: Arc<dyn LanguageModel>, cx: &App) {
let settings = &AllLanguageModelSettings::get_global(cx).ollama;
let http_client = self.http_client.clone();
let api_url = settings.api_url.clone();
@ -203,20 +203,21 @@ impl LanguageModelProvider for OllamaLanguageModelProvider {
.detach_and_log_err(cx);
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
let state = self.state.clone();
cx.new_view(|cx| ConfigurationView::new(state, cx)).into()
cx.new(|cx| ConfigurationView::new(state, window, cx))
.into()
}
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.fetch_models(cx))
}
}
@ -305,7 +306,7 @@ impl LanguageModel for OllamaLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
_cx: &AppContext,
_cx: &App,
) -> BoxFuture<'static, Result<usize>> {
// There is no endpoint for this _yet_ in Ollama
// see: https://github.com/ollama/ollama/issues/1716 and https://github.com/ollama/ollama/issues/3582
@ -407,13 +408,13 @@ impl LanguageModel for OllamaLanguageModel {
}
struct ConfigurationView {
state: gpui::Model<State>,
state: gpui::Entity<State>,
loading_models_task: Option<Task<()>>,
}
impl ConfigurationView {
pub fn new(state: gpui::Model<State>, cx: &mut ViewContext<Self>) -> Self {
let loading_models_task = Some(cx.spawn({
pub fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let loading_models_task = Some(cx.spawn_in(window, {
let state = state.clone();
|this, mut cx| async move {
if let Some(task) = state
@ -436,7 +437,7 @@ impl ConfigurationView {
}
}
fn retry_connection(&self, cx: &mut WindowContext) {
fn retry_connection(&self, cx: &mut App) {
self.state
.update(cx, |state, cx| state.fetch_models(cx))
.detach_and_log_err(cx);
@ -444,7 +445,7 @@ impl ConfigurationView {
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_authenticated = self.state.read(cx).is_authenticated();
let ollama_intro = "Get up and running with Llama 3.3, Mistral, Gemma 2, and other large language models with Ollama.";
@ -498,7 +499,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_SITE))
.on_click(move |_, _, cx| cx.open_url(OLLAMA_SITE))
.into_any_element(),
)
} else {
@ -511,7 +512,9 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_DOWNLOAD_URL))
.on_click(move |_, _, cx| {
cx.open_url(OLLAMA_DOWNLOAD_URL)
})
.into_any_element(),
)
}
@ -522,7 +525,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OLLAMA_LIBRARY_URL)),
.on_click(move |_, _, cx| cx.open_url(OLLAMA_LIBRARY_URL)),
),
)
.child(if is_authenticated {
@ -544,7 +547,9 @@ impl Render for ConfigurationView {
Button::new("retry_ollama_models", "Connect")
.icon_position(IconPosition::Start)
.icon(IconName::ArrowCircle)
.on_click(cx.listener(move |this, _, cx| this.retry_connection(cx)))
.on_click(
cx.listener(move |this, _, _, cx| this.retry_connection(cx)),
)
.into_any_element()
}),
)

View file

@ -3,8 +3,8 @@ use collections::BTreeMap;
use editor::{Editor, EditorElement, EditorStyle};
use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui::{
AnyView, AppContext, AsyncAppContext, FontStyle, ModelContext, Subscription, Task, TextStyle,
View, WhiteSpace,
AnyView, App, AsyncAppContext, Context, Entity, FontStyle, Subscription, Task, TextStyle,
WhiteSpace,
};
use http_client::HttpClient;
use language_model::{
@ -47,7 +47,7 @@ pub struct AvailableModel {
pub struct OpenAiLanguageModelProvider {
http_client: Arc<dyn HttpClient>,
state: gpui::Model<State>,
state: gpui::Entity<State>,
}
pub struct State {
@ -63,7 +63,7 @@ impl State {
self.api_key.is_some()
}
fn reset_api_key(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
let settings = &AllLanguageModelSettings::get_global(cx).openai;
let delete_credentials = cx.delete_credentials(&settings.api_url);
cx.spawn(|this, mut cx| async move {
@ -76,7 +76,7 @@ impl State {
})
}
fn set_api_key(&mut self, api_key: String, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> {
let settings = &AllLanguageModelSettings::get_global(cx).openai;
let write_credentials =
cx.write_credentials(&settings.api_url, "Bearer", api_key.as_bytes());
@ -90,7 +90,7 @@ impl State {
})
}
fn authenticate(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@ -119,8 +119,8 @@ impl State {
}
impl OpenAiLanguageModelProvider {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
let state = cx.new(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_this: &mut State, cx| {
@ -135,7 +135,7 @@ impl OpenAiLanguageModelProvider {
impl LanguageModelProviderState for OpenAiLanguageModelProvider {
type ObservableEntity = State;
fn observable_entity(&self) -> Option<gpui::Model<Self::ObservableEntity>> {
fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
Some(self.state.clone())
}
}
@ -153,7 +153,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
IconName::AiOpenAi
}
fn provided_models(&self, cx: &AppContext) -> Vec<Arc<dyn LanguageModel>> {
fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
let mut models = BTreeMap::default();
// Add base models from open_ai::Model::iter()
@ -194,20 +194,20 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
.collect()
}
fn is_authenticated(&self, cx: &AppContext) -> bool {
fn is_authenticated(&self, cx: &App) -> bool {
self.state.read(cx).is_authenticated()
}
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.authenticate(cx))
}
fn configuration_view(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|cx| ConfigurationView::new(self.state.clone(), cx))
fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView {
cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx))
.into()
}
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>> {
fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
self.state.update(cx, |state, cx| state.reset_api_key(cx))
}
}
@ -215,7 +215,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
pub struct OpenAiLanguageModel {
id: LanguageModelId,
model: open_ai::Model,
state: gpui::Model<State>,
state: gpui::Entity<State>,
http_client: Arc<dyn HttpClient>,
request_limiter: RateLimiter,
}
@ -278,7 +278,7 @@ impl LanguageModel for OpenAiLanguageModel {
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
count_open_ai_tokens(request, self.model.clone(), cx)
}
@ -342,7 +342,7 @@ impl LanguageModel for OpenAiLanguageModel {
pub fn count_open_ai_tokens(
request: LanguageModelRequest,
model: open_ai::Model,
cx: &AppContext,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
cx.background_executor()
.spawn(async move {
@ -372,15 +372,15 @@ pub fn count_open_ai_tokens(
}
struct ConfigurationView {
api_key_editor: View<Editor>,
state: gpui::Model<State>,
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,
load_credentials_task: Option<Task<()>>,
}
impl ConfigurationView {
fn new(state: gpui::Model<State>, cx: &mut ViewContext<Self>) -> Self {
let api_key_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
fn new(state: gpui::Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let api_key_editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
editor
});
@ -390,7 +390,7 @@ impl ConfigurationView {
})
.detach();
let load_credentials_task = Some(cx.spawn({
let load_credentials_task = Some(cx.spawn_in(window, {
let state = state.clone();
|this, mut cx| async move {
if let Some(task) = state
@ -416,14 +416,14 @@ impl ConfigurationView {
}
}
fn save_api_key(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
let api_key = self.api_key_editor.read(cx).text(cx);
if api_key.is_empty() {
return;
}
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.set_api_key(api_key, cx))?
.await
@ -433,12 +433,12 @@ impl ConfigurationView {
cx.notify();
}
fn reset_api_key(&mut self, cx: &mut ViewContext<Self>) {
fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.api_key_editor
.update(cx, |editor, cx| editor.set_text("", cx));
.update(cx, |editor, cx| editor.set_text("", window, cx));
let state = self.state.clone();
cx.spawn(|_, mut cx| async move {
cx.spawn_in(window, |_, mut cx| async move {
state
.update(&mut cx, |state, cx| state.reset_api_key(cx))?
.await
@ -448,7 +448,7 @@ impl ConfigurationView {
cx.notify();
}
fn render_api_key_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
@ -476,13 +476,13 @@ impl ConfigurationView {
)
}
fn should_render_editor(&self, cx: &mut ViewContext<Self>) -> bool {
fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
!self.state.read(cx).is_authenticated()
}
}
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const OPENAI_CONSOLE_URL: &str = "https://platform.openai.com/api-keys";
const INSTRUCTIONS: [&str; 4] = [
"To use Zed's assistant with OpenAI, you need to add an API key. Follow these steps:",
@ -506,7 +506,7 @@ impl Render for ConfigurationView {
.icon(IconName::ExternalLink)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(move |_, cx| cx.open_url(OPENAI_CONSOLE_URL))
.on_click(move |_, _, cx| cx.open_url(OPENAI_CONSOLE_URL))
)
)
.children(
@ -556,9 +556,9 @@ impl Render for ConfigurationView {
.icon_position(IconPosition::Start)
.disabled(env_var_set)
.when(env_var_set, |this| {
this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {OPENAI_API_KEY_VAR} environment variable."), cx))
this.tooltip(Tooltip::text(format!("To reset your API key, unset the {OPENAI_API_KEY_VAR} environment variable.")))
})
.on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))),
.on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))),
)
.into_any()
}

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Result;
use gpui::AppContext;
use gpui::App;
use language_model::LanguageModelCacheConfiguration;
use project::Fs;
use schemars::JsonSchema;
@ -20,7 +20,7 @@ use crate::provider::{
};
/// Initializes the language model settings.
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
AllLanguageModelSettings::register(cx);
if AllLanguageModelSettings::get_global(cx)
@ -246,7 +246,7 @@ impl settings::Settings for AllLanguageModelSettings {
type FileContent = AllLanguageModelSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;