title_bar: Show the plan from the CloudUserStore (#35401)

This PR updates the user menu in the title bar to show the plan from the
`CloudUserStore` instead of the `UserStore`.

We're still leveraging the RPC connection to listen for `UpdateUserPlan`
messages so that we can get live-updates from the server, but we are
merely using this as a signal to re-fetch the information from Cloud.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-07-31 09:56:53 -04:00 committed by GitHub
parent 89ed0b9601
commit 558bbfffae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 94 additions and 19 deletions

View file

@ -2,19 +2,36 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::Context as _;
use cloud_api_client::{AuthenticatedUser, CloudApiClient};
use gpui::{Context, Task};
use chrono::{DateTime, Utc};
use cloud_api_client::{AuthenticatedUser, CloudApiClient, GetAuthenticatedUserResponse, PlanInfo};
use cloud_llm_client::Plan;
use gpui::{Context, Entity, Subscription, Task};
use util::{ResultExt as _, maybe};
use crate::UserStore;
use crate::user::Event as RpcUserStoreEvent;
pub struct CloudUserStore {
cloud_client: Arc<CloudApiClient>,
authenticated_user: Option<Arc<AuthenticatedUser>>,
plan_info: Option<Arc<PlanInfo>>,
_maintain_authenticated_user_task: Task<()>,
_rpc_plan_updated_subscription: Subscription,
}
impl CloudUserStore {
pub fn new(cloud_client: Arc<CloudApiClient>, cx: &mut Context<Self>) -> Self {
pub fn new(
cloud_client: Arc<CloudApiClient>,
rpc_user_store: Entity<UserStore>,
cx: &mut Context<Self>,
) -> Self {
let rpc_plan_updated_subscription =
cx.subscribe(&rpc_user_store, Self::handle_rpc_user_store_event);
Self {
cloud_client: cloud_client.clone(),
authenticated_user: None,
plan_info: None,
_maintain_authenticated_user_task: cx.spawn(async move |this, cx| {
maybe!(async move {
loop {
@ -36,14 +53,15 @@ impl CloudUserStore {
.context("failed to fetch authenticated user");
if let Some(response) = authenticated_user_result.log_err() {
this.update(cx, |this, _cx| {
this.authenticated_user = Some(Arc::new(response.user));
this.update_authenticated_user(response);
})
.ok();
}
}
} else {
this.update(cx, |this, _cx| {
this.authenticated_user = None;
this.authenticated_user.take();
this.plan_info.take();
})
.ok();
}
@ -56,6 +74,7 @@ impl CloudUserStore {
.await
.log_err();
}),
_rpc_plan_updated_subscription: rpc_plan_updated_subscription,
}
}
@ -66,4 +85,56 @@ impl CloudUserStore {
pub fn authenticated_user(&self) -> Option<Arc<AuthenticatedUser>> {
self.authenticated_user.clone()
}
pub fn plan(&self) -> Option<Plan> {
self.plan_info.as_ref().map(|plan| plan.plan)
}
pub fn subscription_period(&self) -> Option<(DateTime<Utc>, DateTime<Utc>)> {
self.plan_info
.as_ref()
.and_then(|plan| plan.subscription_period)
.map(|subscription_period| {
(
subscription_period.started_at.0,
subscription_period.ended_at.0,
)
})
}
fn update_authenticated_user(&mut self, response: GetAuthenticatedUserResponse) {
self.authenticated_user = Some(Arc::new(response.user));
self.plan_info = Some(Arc::new(response.plan));
}
fn handle_rpc_user_store_event(
&mut self,
_: Entity<UserStore>,
event: &RpcUserStoreEvent,
cx: &mut Context<Self>,
) {
match event {
RpcUserStoreEvent::PlanUpdated => {
cx.spawn(async move |this, cx| {
let cloud_client =
cx.update(|cx| this.read_with(cx, |this, _cx| this.cloud_client.clone()))??;
let response = cloud_client
.get_authenticated_user()
.await
.context("failed to fetch authenticated user")?;
cx.update(|cx| {
this.update(cx, |this, _cx| {
this.update_authenticated_user(response);
})
})??;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
_ => {}
}
}
}

View file

@ -145,6 +145,7 @@ pub enum Event {
ShowContacts,
ParticipantIndicesChanged,
PrivateUserInfoUpdated,
PlanUpdated,
}
#[derive(Clone, Copy)]
@ -388,6 +389,7 @@ impl UserStore {
.map(EditPredictionUsage);
}
cx.emit(Event::PlanUpdated);
cx.notify();
})?;
Ok(())