mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
Add an account Summary tab to Analytics (#45769)
## What changed - Make Summary the initial Analytics tab, showing profile identity, token totals, streaks, activity insights, and most-used plugins and skills. - Move daily, weekly, and cumulative token activity charts into Summary. Replace the usage menu's separate views with “View analytics”; `/usage weekly` and the other explicit modes open Summary with the requested view, while reopening from the menu retains navigation. - Load account profiles independently of other reports, preserve missing values separately from zero, and reject responses when the active account changes. Refresh retries profile failures. - Adapt Summary to narrow terminals and compact large chart totals and axis labels. Show the account email without its ID in the Analytics header. - Flush queued transcript history before opening an overlay so startup output stays in the transcript. ## Testing Add profile decoding and account identity tests, Summary layout and navigation snapshots, refresh and cancellation coverage, and regression tests for retained navigation and transcript history when opening Analytics. GitOrigin-RevId: 6dbe6c4c98209000d8b4bc18a4e01b98fab5991a
This commit is contained in:
@@ -37,6 +37,7 @@ use std::fmt;
|
||||
pub(crate) mod analytics;
|
||||
mod chatgpt_turn_cost;
|
||||
pub(crate) mod plan_history;
|
||||
pub(crate) mod profile;
|
||||
mod rate_limit_resets;
|
||||
pub(crate) mod task_usage;
|
||||
mod thread_usage;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Read-only profile statistics shared with the desktop account summary.
|
||||
//! Optional fields retain absence; unknown invocation kinds do not invalidate other statistics.
|
||||
|
||||
use super::Client;
|
||||
use super::RequestError;
|
||||
use crate::TokenUsageProfileStats;
|
||||
use http::Method;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct AccountProfile {
|
||||
pub profile: Option<ProfileIdentity>,
|
||||
pub metadata: Option<ProfileMetadata>,
|
||||
pub stats: ProfileStats,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct ProfileIdentity {
|
||||
pub display_name: Option<String>,
|
||||
pub username: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct ProfileMetadata {
|
||||
pub stats_as_of: Option<String>,
|
||||
pub stats_error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct ProfileStats {
|
||||
#[serde(flatten)]
|
||||
pub tokens: TokenUsageProfileStats,
|
||||
pub fast_mode_usage_percentage: Option<serde_json::Number>,
|
||||
pub most_used_reasoning_effort: Option<String>,
|
||||
pub most_used_reasoning_effort_percentage: Option<serde_json::Number>,
|
||||
pub unique_skills_used: Option<u64>,
|
||||
pub total_skills_used: Option<u64>,
|
||||
pub total_threads: Option<u64>,
|
||||
pub top_invocations: Option<Vec<ProfileInvocation>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct ProfileInvocation {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: ProfileInvocationKind,
|
||||
pub plugin_name: Option<String>,
|
||||
pub skill_name: Option<String>,
|
||||
pub usage_count: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProfileInvocationKind {
|
||||
Plugin,
|
||||
Skill,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Read the full profile using the same route as account token usage.
|
||||
pub async fn get_account_profile(&self) -> Result<AccountProfile, RequestError> {
|
||||
let url = self.token_usage_profile_url();
|
||||
let request = self.request(Method::GET, &url).headers(self.headers());
|
||||
let (body, _) = self.exec_request_detailed(request, "GET", &url).await?;
|
||||
serde_json::from_str(&body)
|
||||
.map_err(|_| RequestError::Other(anyhow::anyhow!("Invalid account profile response.")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "profile_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,126 @@
|
||||
//! Full and partial profile decoding through the authenticated HTTP client.
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use wiremock::Mock;
|
||||
use wiremock::MockServer;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_keeps_missing_zero_and_future_invocations_distinct() {
|
||||
let server = MockServer::start().await;
|
||||
let client = Client::new(
|
||||
server.uri(),
|
||||
codex_http_client::HttpClientFactory::new(
|
||||
codex_http_client::OutboundProxyPolicy::ReqwestDefault,
|
||||
),
|
||||
);
|
||||
let empty = AccountProfile {
|
||||
profile: None,
|
||||
metadata: None,
|
||||
stats: ProfileStats {
|
||||
tokens: TokenUsageProfileStats {
|
||||
lifetime_tokens: None,
|
||||
peak_daily_tokens: None,
|
||||
longest_running_turn_sec: None,
|
||||
current_streak_days: None,
|
||||
longest_streak_days: None,
|
||||
daily_usage_buckets: None,
|
||||
},
|
||||
fast_mode_usage_percentage: None,
|
||||
most_used_reasoning_effort: None,
|
||||
most_used_reasoning_effort_percentage: None,
|
||||
unique_skills_used: None,
|
||||
total_skills_used: None,
|
||||
total_threads: None,
|
||||
top_invocations: None,
|
||||
},
|
||||
};
|
||||
let full = AccountProfile {
|
||||
profile: Some(ProfileIdentity {
|
||||
display_name: Some("Test user".into()),
|
||||
username: Some("test.user".into()),
|
||||
}),
|
||||
metadata: Some(ProfileMetadata {
|
||||
stats_as_of: Some("2026-09-09".into()),
|
||||
stats_error: Some("partial upstream data".into()),
|
||||
}),
|
||||
stats: ProfileStats {
|
||||
tokens: TokenUsageProfileStats {
|
||||
lifetime_tokens: Some(142_300_000_000),
|
||||
peak_daily_tokens: Some(7_000_000_000),
|
||||
longest_running_turn_sec: Some(158_760),
|
||||
current_streak_days: Some(158),
|
||||
longest_streak_days: Some(160),
|
||||
daily_usage_buckets: Some(vec![crate::TokenUsageProfileDailyBucket {
|
||||
start_date: "2026-09-09".into(),
|
||||
tokens: 42,
|
||||
}]),
|
||||
},
|
||||
fast_mode_usage_percentage: serde_json::Number::from_f64(/*f*/ 37.5),
|
||||
most_used_reasoning_effort: Some("high".into()),
|
||||
most_used_reasoning_effort_percentage: Some(26.into()),
|
||||
unique_skills_used: Some(147),
|
||||
total_skills_used: Some(13_190),
|
||||
total_threads: Some(0),
|
||||
top_invocations: Some(vec![
|
||||
ProfileInvocation {
|
||||
kind: ProfileInvocationKind::Unknown,
|
||||
plugin_name: None,
|
||||
skill_name: None,
|
||||
usage_count: None,
|
||||
},
|
||||
ProfileInvocation {
|
||||
kind: ProfileInvocationKind::Skill,
|
||||
plugin_name: None,
|
||||
skill_name: Some("review".into()),
|
||||
usage_count: Some(12),
|
||||
},
|
||||
ProfileInvocation {
|
||||
kind: ProfileInvocationKind::Plugin,
|
||||
plugin_name: Some("github".into()),
|
||||
skill_name: None,
|
||||
usage_count: Some(34),
|
||||
},
|
||||
]),
|
||||
},
|
||||
};
|
||||
for (body, expected) in [
|
||||
(json!({"stats":{}}), empty),
|
||||
(
|
||||
json!({
|
||||
"profile":{"display_name":"Test user","username":"test.user"},
|
||||
"metadata":{"stats_as_of":"2026-09-09","stats_error":"partial upstream data"},
|
||||
"stats":{
|
||||
"lifetime_tokens":142_300_000_000_i64,"peak_daily_tokens":7_000_000_000_i64,
|
||||
"longest_running_turn_sec":158_760,"current_streak_days":158,"longest_streak_days":160,
|
||||
"daily_usage_buckets":[{"start_date":"2026-09-09","tokens":42}],
|
||||
"fast_mode_usage_percentage":37.5,"most_used_reasoning_effort":"high",
|
||||
"most_used_reasoning_effort_percentage":26,"unique_skills_used":147,
|
||||
"total_skills_used":13_190,"total_threads":0,
|
||||
"top_invocations":[{"type":"future_kind"},{"type":"skill","skill_name":"review","usage_count":12},
|
||||
{"type":"plugin","plugin_name":"github","usage_count":34}]
|
||||
}
|
||||
}),
|
||||
full,
|
||||
),
|
||||
] {
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/api/codex/profiles/me"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 200).set_body_json(body))
|
||||
.expect(/*r*/ 1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
assert_eq!(client.get_account_profile().await.unwrap(), expected);
|
||||
server.verify().await;
|
||||
server.reset().await;
|
||||
}
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 200).set_body_string("private invalid payload"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
let error = client.get_account_profile().await.unwrap_err().to_string();
|
||||
assert_eq!(error, "Invalid account profile response.");
|
||||
}
|
||||
@@ -13,6 +13,12 @@ pub use client::ThreadUsage;
|
||||
pub use client::ThreadUsageBreakdownGroup;
|
||||
pub use client::analytics::AnalyticsReport;
|
||||
pub use client::analytics::AnalyticsResponse;
|
||||
pub use client::profile::AccountProfile;
|
||||
pub use client::profile::ProfileIdentity;
|
||||
pub use client::profile::ProfileInvocation;
|
||||
pub use client::profile::ProfileInvocationKind;
|
||||
pub use client::profile::ProfileMetadata;
|
||||
pub use client::profile::ProfileStats;
|
||||
pub use client::turn_usage::ApiKeyResponseCost;
|
||||
pub use client::turn_usage::ApiKeyTurnCost;
|
||||
pub use client::turn_usage::ApiKeyTurnCostStatus;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
//! Authenticated account analytics dashboard.
|
||||
//! A maximized section and an overview share bounded report loads, selection, and inline details.
|
||||
|
||||
mod activity_chart;
|
||||
mod chart;
|
||||
mod summary;
|
||||
mod summary_panel;
|
||||
pub(crate) use activity_chart::TokenActivityView;
|
||||
mod chat_panel;
|
||||
mod chats;
|
||||
mod client;
|
||||
@@ -53,6 +57,7 @@ pub(crate) struct AnalyticsView {
|
||||
chats: Load<chats::Chats>,
|
||||
tasks: Load<tasks::Chats>,
|
||||
chat_metric: usize,
|
||||
profile: Load<codex_backend_client::AccountProfile>,
|
||||
plan: plan::State,
|
||||
show_zero_credit_groups: bool,
|
||||
account: Load<codex_protocol::account::PlanType>,
|
||||
@@ -84,6 +89,7 @@ impl AnalyticsView {
|
||||
chats: Load::Unavailable,
|
||||
tasks: Load::Unavailable,
|
||||
chat_metric: 0,
|
||||
profile: Load::Unavailable,
|
||||
plan: plan::State::default(),
|
||||
show_zero_credit_groups: false,
|
||||
account: Load::Unavailable,
|
||||
@@ -91,7 +97,7 @@ impl AnalyticsView {
|
||||
connection: None,
|
||||
live: None,
|
||||
keymap,
|
||||
section: Section::Usage,
|
||||
section: Section::Summary,
|
||||
zoomed: true,
|
||||
scroll_offset: 0,
|
||||
follow_selection: true,
|
||||
@@ -144,6 +150,7 @@ impl AnalyticsView {
|
||||
self.sections[Section::Chats].detail = None;
|
||||
self.chats = Load::Unavailable;
|
||||
self.tasks = Load::Unavailable;
|
||||
self.profile = Load::Unavailable;
|
||||
self.plan.report = Load::Unavailable;
|
||||
self.reports_started = false;
|
||||
self.token_model = None;
|
||||
@@ -176,6 +183,7 @@ impl AnalyticsView {
|
||||
}
|
||||
self.chats = Load::Unavailable;
|
||||
self.tasks = Load::Unavailable;
|
||||
self.profile = Load::Unavailable;
|
||||
self.plan.report = Load::Unavailable;
|
||||
self.account = Load::Unavailable;
|
||||
self.live = None;
|
||||
@@ -217,6 +225,7 @@ impl AnalyticsView {
|
||||
return &[];
|
||||
}
|
||||
match self.section {
|
||||
Section::Summary => &[0, 1, 2],
|
||||
Section::Plan => &[1, 2, 0, 3],
|
||||
Section::Usage if self.business() => &[6, 2],
|
||||
Section::Usage if self.consumer_attribution() => &[1, 2, 0, 3],
|
||||
@@ -246,6 +255,7 @@ impl AnalyticsView {
|
||||
Section::Skills => "Skills used",
|
||||
Section::Chats => "Top chats",
|
||||
Section::Plan => "Plan usage history",
|
||||
Section::Summary => "Summary",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +369,10 @@ impl AnalyticsView {
|
||||
return;
|
||||
}
|
||||
if key_hint::plain(KeyCode::Char('r')).is_press(key)
|
||||
&& !matches!(self.section, Section::Chats | Section::Plan)
|
||||
&& !matches!(
|
||||
self.section,
|
||||
Section::Chats | Section::Plan | Section::Summary
|
||||
)
|
||||
&& !self.visible_sections().is_empty()
|
||||
{
|
||||
self.change_range();
|
||||
@@ -382,7 +395,7 @@ impl AnalyticsView {
|
||||
.position(|section| *section == self.section)
|
||||
.unwrap_or_default();
|
||||
let target = match key.code {
|
||||
KeyCode::Char(n @ '1'..='6') if key.modifiers.is_empty() => {
|
||||
KeyCode::Char(n @ '1'..='7') if key.modifiers.is_empty() => {
|
||||
visible.get(n as usize - '1' as usize).copied()
|
||||
}
|
||||
KeyCode::Tab => Some(visible[(current + 1) % visible.len()]),
|
||||
@@ -397,6 +410,10 @@ impl AnalyticsView {
|
||||
self.group_picker = None;
|
||||
return;
|
||||
}
|
||||
if self.section == Section::Summary {
|
||||
self.summary_action(action);
|
||||
return;
|
||||
}
|
||||
if self.section == Section::Plan {
|
||||
self.plan_action(action);
|
||||
return;
|
||||
@@ -494,6 +511,7 @@ impl AnalyticsView {
|
||||
}
|
||||
self.chats = Load::Unavailable;
|
||||
self.tasks = Load::Unavailable;
|
||||
self.profile = Load::Unavailable;
|
||||
self.plan.report = Load::Unavailable;
|
||||
self.account = Load::Unavailable;
|
||||
self.connection = None;
|
||||
|
||||
@@ -10,6 +10,7 @@ fn account_layouts_show_reports_and_wrap_navigation() {
|
||||
(
|
||||
models::AccountKind::Consumer,
|
||||
vec![
|
||||
Section::Summary,
|
||||
Section::Usage,
|
||||
Section::Activity,
|
||||
Section::Plugins,
|
||||
@@ -20,6 +21,7 @@ fn account_layouts_show_reports_and_wrap_navigation() {
|
||||
(
|
||||
models::AccountKind::Enterprise,
|
||||
vec![
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
@@ -52,6 +54,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
let server = test_support::server().await;
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, "plus").await;
|
||||
test_support::settle(&mut view).await;
|
||||
view.section = Section::Usage;
|
||||
press(&mut view, KeyCode::Left);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
@@ -67,7 +70,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
view.sections[Section::Usage].detail,
|
||||
view.sections[Section::Usage].group
|
||||
),
|
||||
([1, 0, 0], [28, 6, 6, 0, 6, 6, 6], Some(28), 2)
|
||||
([1, 0, 0], [28, 6, 6, 0, 6, 6, 6, 6], Some(28), 2)
|
||||
);
|
||||
let context = (
|
||||
view.ranges,
|
||||
@@ -87,7 +90,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
),
|
||||
context
|
||||
);
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(view.ranges, [1, 1, 0]);
|
||||
@@ -96,7 +99,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
.0
|
||||
.each_ref()
|
||||
.map(|state| state.history.ready().map(|history| history.data.len())),
|
||||
[Some(30), Some(7), None, None, Some(30), Some(7), None]
|
||||
[Some(30), Some(7), None, None, Some(30), Some(7), None, None]
|
||||
);
|
||||
let server = test_support::server().await;
|
||||
let (_business_home, _business_server, mut view) =
|
||||
@@ -104,7 +107,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
test_support::settle(&mut view).await;
|
||||
view.ranges = [1, 1, 0];
|
||||
view.load_report(Section::Usage);
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(view.ranges, [0, 1, 0]);
|
||||
@@ -113,7 +116,7 @@ async fn changing_ranges_and_grouping_preserves_other_reports_and_focus() {
|
||||
.0
|
||||
.each_ref()
|
||||
.map(|state| state.history.ready().map(|history| history.data.len())),
|
||||
[Some(30), Some(7), Some(7), None, None, Some(7), None]
|
||||
[Some(30), Some(7), Some(7), None, None, Some(7), None, None]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,7 +127,7 @@ async fn business_tokens_filter_models() {
|
||||
test_support::settle(&mut view).await;
|
||||
view.end_date = fixture::END_DATE;
|
||||
fixture::seed_reports(&mut view);
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
let all = screen(&mut view, /*width*/ 100, /*height*/ 30);
|
||||
assert!(all.contains("All models") && all.contains("Cached input"));
|
||||
press(&mut view, KeyCode::Char('m'));
|
||||
@@ -194,6 +197,14 @@ async fn account_reports_request_only_eligible_endpoints_and_refresh() {
|
||||
),
|
||||
] {
|
||||
let server = test_support::server().await;
|
||||
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/profiles/me"))
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(/*s*/ 200)
|
||||
.set_body_json(serde_json::json!({"stats":{}})),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, plan).await;
|
||||
test_support::settle(&mut view).await;
|
||||
let mut expected = [
|
||||
@@ -202,6 +213,7 @@ async fn account_reports_request_only_eligible_endpoints_and_refresh() {
|
||||
activity,
|
||||
"analytics/daily-plugin-usage-metrics",
|
||||
"analytics/daily-skill-usage-metrics",
|
||||
"profiles/me",
|
||||
]
|
||||
.map(|endpoint| format!("/backend-api/wham/{endpoint}"));
|
||||
expected.sort();
|
||||
@@ -223,9 +235,9 @@ async fn account_reports_request_only_eligible_endpoints_and_refresh() {
|
||||
assert_eq!(
|
||||
ready,
|
||||
if plan == "plus" {
|
||||
[true, true, false, false, true, true, false]
|
||||
[true, true, false, false, true, true, false, false]
|
||||
} else {
|
||||
[true, true, true, false, false, true, false]
|
||||
[true, true, true, false, false, true, false, false]
|
||||
}
|
||||
);
|
||||
press(&mut view, KeyCode::Char('R'));
|
||||
@@ -245,7 +257,7 @@ async fn account_reports_request_only_eligible_endpoints_and_refresh() {
|
||||
.iter()
|
||||
.filter(|request| request.url.path().starts_with("/backend-api/wham/"))
|
||||
.count(),
|
||||
10
|
||||
12
|
||||
);
|
||||
view.cancel_loads();
|
||||
}
|
||||
@@ -255,6 +267,7 @@ async fn account_reports_request_only_eligible_endpoints_and_refresh() {
|
||||
async fn legacy_response_closes_an_attribution_picker_that_is_no_longer_valid() {
|
||||
let server = test_support::server().await;
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, "plus").await;
|
||||
view.section = Section::Usage;
|
||||
view.account = Load::Ready(codex_protocol::account::PlanType::Plus);
|
||||
view.start_reports();
|
||||
view.group_picker = Some(3);
|
||||
@@ -275,6 +288,7 @@ async fn legacy_response_invalidates_picker_before_the_next_draw() {
|
||||
let server = test_support::server().await;
|
||||
for choice in [2, 3] {
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, "plus").await;
|
||||
view.section = Section::Usage;
|
||||
view.account = Load::Ready(codex_protocol::account::PlanType::Plus);
|
||||
view.start_reports();
|
||||
view.group_picker = Some(choice);
|
||||
@@ -305,7 +319,7 @@ fn business_chats_display_only_supplied_dollars() {
|
||||
usage.estimated_usage_usd_micros = None;
|
||||
}
|
||||
}
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
assert!(!screen(&mut view, /*width*/ 100, /*height*/ 30).contains("Est. $ spent"));
|
||||
if let Load::Ready(chats) = &mut view.chats {
|
||||
chats.rows[0]
|
||||
@@ -338,6 +352,7 @@ async fn unsupported_workspace_plans_hide_top_chats_and_skip_loading() {
|
||||
assert_eq!(
|
||||
view.visible_sections(),
|
||||
&[
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
@@ -347,7 +362,7 @@ async fn unsupported_workspace_plans_hide_top_chats_and_skip_loading() {
|
||||
);
|
||||
assert!(matches!(view.chats, Load::Unavailable), "{plan}");
|
||||
let selected = view.section;
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
assert_eq!(view.section, selected);
|
||||
}
|
||||
for plan in [
|
||||
@@ -359,10 +374,62 @@ async fn unsupported_workspace_plans_hide_top_chats_and_skip_loading() {
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, plan).await;
|
||||
test_support::settle(&mut view).await;
|
||||
assert!(view.visible_sections().contains(&Section::Chats), "{plan}");
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
assert_eq!(view.section, Section::Chats);
|
||||
}
|
||||
let mut view = fixture::view(models::AccountKind::Enterprise);
|
||||
view.account = Load::Ready(codex_protocol::account::PlanType::Enterprise);
|
||||
insta::assert_snapshot!(screen(&mut view, /*width*/ 110, /*height*/ 24));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_plan_keeps_summary_available_without_billing_reports() {
|
||||
let server = test_support::server().await;
|
||||
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/profiles/me"))
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(/*s*/ 200)
|
||||
.set_body_json(serde_json::json!({"stats":{"lifetime_tokens":12345}})),
|
||||
)
|
||||
.expect(/*r*/ 1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let (_home, _app_server, mut view) =
|
||||
client::tests::connected_view(&server, "future-plan").await;
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(view.visible_sections(), &[Section::Summary]);
|
||||
assert_eq!(
|
||||
view.profile.ready().unwrap().stats.tokens.lifetime_tokens,
|
||||
Some(12345)
|
||||
);
|
||||
assert!(
|
||||
view.sections
|
||||
.0
|
||||
.iter()
|
||||
.all(|section| matches!(section.history, Load::Unavailable))
|
||||
);
|
||||
let paths = server
|
||||
.received_requests()
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|request| request.url.path().starts_with("/backend-api/wham/"))
|
||||
.map(|request| request.url.path().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
paths,
|
||||
[
|
||||
"/backend-api/wham/accounts/check",
|
||||
"/backend-api/wham/profiles/me"
|
||||
]
|
||||
);
|
||||
view.end_date = "2026-09-09".parse().unwrap();
|
||||
let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(
|
||||
/*width*/ 100, /*height*/ 40,
|
||||
))
|
||||
.unwrap();
|
||||
terminal
|
||||
.draw(|frame| view.render(frame.area(), frame.buffer_mut()))
|
||||
.unwrap();
|
||||
insta::assert_snapshot!(terminal.backend().to_string());
|
||||
}
|
||||
|
||||
+28
-183
@@ -1,8 +1,4 @@
|
||||
//! Renders account token usage summaries and activity charts for `/usage`.
|
||||
//!
|
||||
//! This module owns the chart data bucketing and ratatui line construction. The
|
||||
//! async card lifecycle stays in the parent `tokens` module so chart rendering
|
||||
//! remains a pure transformation from a loaded usage response.
|
||||
//! Calendar and weekly token charts for the account Summary. Bucketing is independent of view state.
|
||||
|
||||
mod palette;
|
||||
|
||||
@@ -11,22 +7,18 @@ use std::collections::BTreeMap;
|
||||
use chrono::Datelike;
|
||||
use chrono::Duration;
|
||||
use chrono::NaiveDate;
|
||||
use codex_app_server_protocol::GetAccountTokenUsageResponse;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::text::Span;
|
||||
|
||||
use crate::render::highlight::foreground_style_for_scopes;
|
||||
use crate::status::format_tokens_compact;
|
||||
use palette::TokenActivityPalette;
|
||||
|
||||
const WEEK_COUNT: usize = 52;
|
||||
const DAY_COUNT: usize = 7;
|
||||
const CELL_COUNT: usize = WEEK_COUNT * DAY_COUNT;
|
||||
const CHART_LEFT_WIDTH: usize = 4;
|
||||
const SUMMARY_INDENT: &str = " ";
|
||||
const SUMMARY_INDENT_WIDTH: u16 = 1;
|
||||
|
||||
/// Selects the aggregation represented by the token activity chart.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -39,10 +31,10 @@ pub(crate) enum TokenActivityView {
|
||||
impl TokenActivityView {
|
||||
/// Parses the optional `/usage` argument into a supported chart view.
|
||||
///
|
||||
/// An empty argument selects the daily view so `/usage` and `/usage daily`
|
||||
/// behave identically. Returning `None` lets the slash-command dispatcher
|
||||
/// An empty argument selects Daily when a mode is requested. The bare `/usage`
|
||||
/// command opens its menu. Returning `None` lets the slash-command dispatcher
|
||||
/// report unsupported arguments instead of silently choosing a view.
|
||||
pub(in crate::chatwidget) fn parse(value: &str) -> Option<Self> {
|
||||
pub(crate) fn parse(value: &str) -> Option<Self> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"" | "day" | "daily" => Some(Self::Daily),
|
||||
"week" | "weekly" => Some(Self::Weekly),
|
||||
@@ -51,7 +43,7 @@ impl TokenActivityView {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn label(self) -> &'static str {
|
||||
pub(crate) fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Daily => "Daily",
|
||||
Self::Weekly => "Weekly",
|
||||
@@ -60,34 +52,9 @@ impl TokenActivityView {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn loaded_lines(
|
||||
pub(super) fn chart_lines(
|
||||
view: TokenActivityView,
|
||||
response: &GetAccountTokenUsageResponse,
|
||||
today: NaiveDate,
|
||||
width: u16,
|
||||
) -> Vec<Line<'static>> {
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
Span::from(" Token activity").bold(),
|
||||
Span::styled(" last 12 months", label_style()),
|
||||
]
|
||||
.into(),
|
||||
];
|
||||
lines.extend(summary_lines(response, graph_width(width)));
|
||||
// Separate the headline numbers from the calendar below.
|
||||
lines.push(Line::default());
|
||||
let Some(buckets) = response.daily_usage_buckets.as_ref() else {
|
||||
lines.push(" Token activity history unavailable".dim().into());
|
||||
return lines;
|
||||
};
|
||||
|
||||
lines.extend(chart_lines(view, buckets, today, width));
|
||||
lines
|
||||
}
|
||||
|
||||
fn chart_lines(
|
||||
view: TokenActivityView,
|
||||
buckets: &[codex_app_server_protocol::AccountTokenUsageDailyBucket],
|
||||
buckets: &[codex_backend_client::TokenUsageProfileDailyBucket],
|
||||
today: NaiveDate,
|
||||
width: u16,
|
||||
) -> Vec<Line<'static>> {
|
||||
@@ -133,7 +100,6 @@ fn chart_lines(
|
||||
lines.push(bar_caption(view, &values))
|
||||
}
|
||||
}
|
||||
lines.push(view_footer(view));
|
||||
lines
|
||||
}
|
||||
|
||||
@@ -145,114 +111,6 @@ fn shown_columns(width: u16) -> usize {
|
||||
.min(WEEK_COUNT)
|
||||
}
|
||||
|
||||
fn graph_width(width: u16) -> u16 {
|
||||
if width == u16::MAX {
|
||||
return width;
|
||||
}
|
||||
(CHART_LEFT_WIDTH + shown_columns(width) * 2 - 1) as u16
|
||||
}
|
||||
|
||||
fn summary_lines(response: &GetAccountTokenUsageResponse, width: u16) -> Vec<Line<'static>> {
|
||||
let summary = &response.summary;
|
||||
let fields = [
|
||||
("Lifetime", format_optional_tokens(summary.lifetime_tokens)),
|
||||
("Peak", format_optional_tokens(summary.peak_daily_tokens)),
|
||||
(
|
||||
"Streak",
|
||||
format_streak(summary.current_streak_days, summary.longest_streak_days),
|
||||
),
|
||||
(
|
||||
"Longest task",
|
||||
format_optional_duration(summary.longest_running_turn_sec),
|
||||
),
|
||||
];
|
||||
pack_fields(&fields, width)
|
||||
.into_iter()
|
||||
.map(|group| align_summary_line(summary_line(&fields, &group), width))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Greedily pack summary fields into as few lines as fit `width`,
|
||||
/// keeping field order. `u16::MAX` (raw/copy mode) always yields one line.
|
||||
fn pack_fields(fields: &[(&str, String)], width: u16) -> Vec<Vec<usize>> {
|
||||
if width == u16::MAX {
|
||||
return vec![(0..fields.len()).collect()];
|
||||
}
|
||||
let max = usize::from(width.saturating_sub(SUMMARY_INDENT_WIDTH));
|
||||
let mut groups: Vec<Vec<usize>> = Vec::new();
|
||||
let mut current: Vec<usize> = Vec::new();
|
||||
for index in 0..fields.len() {
|
||||
let mut candidate = current.clone();
|
||||
candidate.push(index);
|
||||
if !current.is_empty() && summary_line(fields, &candidate).width() > max {
|
||||
groups.push(std::mem::take(&mut current));
|
||||
current.push(index);
|
||||
} else {
|
||||
current = candidate;
|
||||
}
|
||||
}
|
||||
if !current.is_empty() {
|
||||
groups.push(current);
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
fn summary_line(fields: &[(&str, String)], indexes: &[usize]) -> Line<'static> {
|
||||
let mut spans = Vec::new();
|
||||
for (index, field_index) in indexes.iter().enumerate() {
|
||||
if index > 0 {
|
||||
spans.push(Span::styled(" · ", label_style()));
|
||||
}
|
||||
let (label, value) = &fields[*field_index];
|
||||
spans.push(Span::styled(format!("{label} "), label_style()));
|
||||
spans.push(Span::styled(value.clone(), numeric_style()));
|
||||
}
|
||||
spans.into()
|
||||
}
|
||||
|
||||
fn align_summary_line(mut line: Line<'static>, width: u16) -> Line<'static> {
|
||||
if width == u16::MAX {
|
||||
return line;
|
||||
}
|
||||
line.spans.insert(/*index*/ 0, SUMMARY_INDENT.into());
|
||||
line
|
||||
}
|
||||
|
||||
fn format_optional_tokens(value: Option<i64>) -> String {
|
||||
value
|
||||
.map(format_tokens_compact)
|
||||
.unwrap_or_else(|| "-".to_string())
|
||||
}
|
||||
|
||||
/// Combine the current and longest streak into one field: a bare `54d` when
|
||||
/// they match, otherwise `12d (best 54d)`.
|
||||
fn format_streak(current: Option<i64>, longest: Option<i64>) -> String {
|
||||
match (current, longest) {
|
||||
(Some(current), Some(longest)) if current == longest => format!("{current}d"),
|
||||
(Some(current), Some(longest)) => format!("{current}d (best {longest}d)"),
|
||||
(Some(current), None) => format!("{current}d"),
|
||||
(None, Some(longest)) => format!("- (best {longest}d)"),
|
||||
(None, None) => "-".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_optional_duration(value: Option<i64>) -> String {
|
||||
value.map_or_else(
|
||||
|| "-".to_string(),
|
||||
|seconds| {
|
||||
let seconds = seconds.max(/*other*/ 0);
|
||||
let hours = seconds / 3600;
|
||||
let minutes = (seconds % 3600) / 60;
|
||||
match (hours, minutes) {
|
||||
(0, 0) => format!("{seconds}s"),
|
||||
(0, minutes) => format!("{minutes}m"),
|
||||
(hours, 0) => format!("{hours}h"),
|
||||
(hours, minutes) => format!("{hours}h {minutes}m"),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn numeric_style() -> Style {
|
||||
foreground_style_for_scopes(&["constant.numeric", "constant"])
|
||||
.unwrap_or_else(|| Style::default().green())
|
||||
@@ -314,7 +172,12 @@ fn bar_caption(view: TokenActivityView, values: &[i64]) -> Line<'static> {
|
||||
"Each column = 1 week · tallest ",
|
||||
weeks.iter().copied().max().unwrap_or(/*default*/ 0),
|
||||
),
|
||||
TokenActivityView::Cumulative => ("Running total · top ", weeks.iter().sum::<i64>()),
|
||||
TokenActivityView::Cumulative => (
|
||||
"Running total · top ",
|
||||
weeks
|
||||
.iter()
|
||||
.fold(/*init*/ 0_i64, |sum, value| sum.saturating_add(*value)),
|
||||
),
|
||||
TokenActivityView::Daily => ("", 0),
|
||||
};
|
||||
if peak <= 0 {
|
||||
@@ -322,34 +185,11 @@ fn bar_caption(view: TokenActivityView, values: &[i64]) -> Line<'static> {
|
||||
}
|
||||
vec![
|
||||
Span::styled(format!(" {lead}"), label_style()),
|
||||
Span::styled(format_tokens_compact(peak), numeric_style()),
|
||||
Span::styled(super::data::compact_amount(peak as f64), numeric_style()),
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Dim footer that surfaces the other `/usage` views and emphasizes the
|
||||
/// active one, so the weekly/cumulative modes are discoverable from the card.
|
||||
fn view_footer(active: TokenActivityView) -> Line<'static> {
|
||||
let mut spans = vec![Span::styled(" ", label_style())];
|
||||
let views = [
|
||||
(TokenActivityView::Daily, "daily"),
|
||||
(TokenActivityView::Weekly, "weekly"),
|
||||
(TokenActivityView::Cumulative, "cumulative"),
|
||||
];
|
||||
for (index, (view, name)) in views.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
spans.push(Span::styled(" · ", label_style()));
|
||||
}
|
||||
let style = if view == active {
|
||||
numeric_style().bold()
|
||||
} else {
|
||||
label_style()
|
||||
};
|
||||
spans.push(Span::styled(name, style));
|
||||
}
|
||||
spans.into()
|
||||
}
|
||||
|
||||
fn month_labels(today: NaiveDate, first_column: usize, shown_columns: usize) -> Line<'static> {
|
||||
let mut cells = vec![' '; shown_columns * 2 - 1];
|
||||
let start = chart_start(today);
|
||||
@@ -382,7 +222,7 @@ fn month_labels(today: NaiveDate, first_column: usize, shown_columns: usize) ->
|
||||
/// Invalid, out-of-window, and future dates are ignored. Duplicate dates are
|
||||
/// accumulated and negative token values do not reduce activity.
|
||||
fn daily_values(
|
||||
buckets: &[codex_app_server_protocol::AccountTokenUsageDailyBucket],
|
||||
buckets: &[codex_backend_client::TokenUsageProfileDailyBucket],
|
||||
today: NaiveDate,
|
||||
) -> Vec<i64> {
|
||||
let start = chart_start(today);
|
||||
@@ -395,7 +235,8 @@ fn daily_values(
|
||||
if date < start || date >= end || date > today {
|
||||
continue;
|
||||
}
|
||||
*by_date.entry(date).or_insert(/*default*/ 0) += bucket.tokens.max(/*other*/ 0);
|
||||
let value: &mut i64 = by_date.entry(date).or_insert(/*default*/ 0);
|
||||
*value = value.saturating_add(bucket.tokens.max(/*other*/ 0));
|
||||
}
|
||||
(0..CELL_COUNT)
|
||||
.map(|offset| {
|
||||
@@ -415,7 +256,7 @@ fn levels_for_view(values: &[i64], view: TokenActivityView) -> Vec<usize> {
|
||||
let cumulative = weekly_totals(values)
|
||||
.into_iter()
|
||||
.scan(/*initial_state*/ 0, |sum, value| {
|
||||
*sum += value;
|
||||
*sum = i64::saturating_add(*sum, value);
|
||||
Some(*sum)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -430,9 +271,9 @@ fn graded_levels(values: &[i64]) -> Vec<usize> {
|
||||
.iter()
|
||||
.map(|value| match (*value, max) {
|
||||
(0, _) | (_, 0) => 0,
|
||||
(value, max) if value * 4 > max * 3 => 4,
|
||||
(value, max) if value * 2 > max => 3,
|
||||
(value, max) if value * 4 > max => 2,
|
||||
(value, max) if i128::from(value) * 4 > i128::from(max) * 3 => 4,
|
||||
(value, max) if i128::from(value) * 2 > i128::from(max) => 3,
|
||||
(value, max) if i128::from(value) * 4 > i128::from(max) => 2,
|
||||
_ => 1,
|
||||
})
|
||||
.collect()
|
||||
@@ -441,7 +282,10 @@ fn graded_levels(values: &[i64]) -> Vec<usize> {
|
||||
fn weekly_totals(values: &[i64]) -> Vec<i64> {
|
||||
values
|
||||
.chunks(DAY_COUNT)
|
||||
.map(|week| week.iter().sum())
|
||||
.map(|week| {
|
||||
week.iter()
|
||||
.fold(/*init*/ 0_i64, |sum, value| sum.saturating_add(*value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -453,7 +297,8 @@ fn bar_levels(totals: &[i64]) -> Vec<usize> {
|
||||
let height = if *value <= 0 || max <= 0 {
|
||||
0
|
||||
} else {
|
||||
((*value * DAY_COUNT as i64 + max - 1) / max) as usize
|
||||
((i128::from(*value) * DAY_COUNT as i128 + i128::from(max) - 1) / i128::from(max))
|
||||
as usize
|
||||
};
|
||||
(0..DAY_COUNT).map(move |row| if DAY_COUNT - row <= height { 4 } else { 0 })
|
||||
})
|
||||
@@ -470,5 +315,5 @@ fn cell_date(today: NaiveDate, index: usize) -> Option<NaiveDate> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "chart_tests.rs"]
|
||||
#[path = "activity_chart_tests.rs"]
|
||||
mod tests;
|
||||
+44
-49
@@ -1,9 +1,16 @@
|
||||
//! Calendar, aggregation, and chart rendering regressions.
|
||||
use super::*;
|
||||
use codex_app_server_protocol::AccountTokenUsageDailyBucket;
|
||||
use codex_app_server_protocol::AccountTokenUsageSummary;
|
||||
use codex_backend_client::TokenUsageProfileDailyBucket as AccountTokenUsageDailyBucket;
|
||||
use insta::assert_snapshot;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn graph_width(width: u16) -> u16 {
|
||||
if width == u16::MAX {
|
||||
return width;
|
||||
}
|
||||
(CHART_LEFT_WIDTH + shown_columns(width) * 2 - 1) as u16
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_dates_sum_and_negative_values_clamp() {
|
||||
let today =
|
||||
@@ -86,7 +93,6 @@ fn daily_graph_snapshot_uses_distinct_empty_and_active_cells() {
|
||||
Sa □ □ □ □ □ □ □ □
|
||||
|
||||
Less □ ■ ■ ■ ■ More
|
||||
daily · weekly · cumulative
|
||||
");
|
||||
}
|
||||
|
||||
@@ -105,9 +111,9 @@ fn daily_graph_snapshot_stays_left_aligned_in_wide_terminal() {
|
||||
.join("\n");
|
||||
|
||||
assert_snapshot!(rendered, @"
|
||||
Jun Jul Aug Sep Oct Nov Dec Jan Feb Mar Apr May
|
||||
Su □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
|
||||
daily · weekly · cumulative
|
||||
Jun Jul Aug Sep Oct Nov Dec Jan Feb Mar Apr May
|
||||
Su □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
|
||||
Less □ ■ ■ ■ ■ More
|
||||
");
|
||||
}
|
||||
|
||||
@@ -152,8 +158,7 @@ fn weekly_graph_snapshot_renders_bar_chart_and_caption() {
|
||||
0 █ █ █
|
||||
|
||||
Each column = 1 week · tallest 9
|
||||
daily · weekly · cumulative
|
||||
");
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -197,48 +202,38 @@ fn cumulative_graph_snapshot_renders_running_total_bar_chart_and_caption() {
|
||||
0 █ █ █
|
||||
|
||||
Running total · top 18
|
||||
daily · weekly · cumulative
|
||||
");
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_snapshot_left_aligns_and_splits_when_needed() {
|
||||
let response = GetAccountTokenUsageResponse {
|
||||
summary: AccountTokenUsageSummary {
|
||||
lifetime_tokens: Some(21_400_000_000),
|
||||
peak_daily_tokens: Some(835_000_000),
|
||||
longest_running_turn_sec: Some(13_920),
|
||||
current_streak_days: Some(54),
|
||||
longest_streak_days: Some(54),
|
||||
},
|
||||
daily_usage_buckets: None,
|
||||
thread_usage: None,
|
||||
};
|
||||
let rendered = |width| {
|
||||
summary_lines(&response, graph_width(width))
|
||||
.into_iter()
|
||||
.map(|line| line.to_string().trim_end().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
assert_snapshot!(
|
||||
format!(
|
||||
"wide:\n{}\n\nnarrow:\n{}\n\ntight:\n{}",
|
||||
rendered(/*width*/ 120),
|
||||
rendered(/*width*/ 80),
|
||||
rendered(/*width*/ 62)
|
||||
),
|
||||
@"
|
||||
wide:
|
||||
Lifetime 21.4B · Peak 835M · Streak 54d · Longest task 3h 52m
|
||||
|
||||
narrow:
|
||||
Lifetime 21.4B · Peak 835M · Streak 54d · Longest task 3h 52m
|
||||
|
||||
tight:
|
||||
Lifetime 21.4B · Peak 835M · Streak 54d
|
||||
Longest task 3h 52m
|
||||
"
|
||||
);
|
||||
fn year_window_ignores_other_dates_and_saturates_extreme_totals() {
|
||||
let today: NaiveDate = "2026-09-09".parse().unwrap();
|
||||
let buckets = [
|
||||
("2025-01-01", 100),
|
||||
("2026-09-10", 100),
|
||||
("invalid", 100),
|
||||
("2026-09-08", i64::MAX),
|
||||
("2026-09-08", i64::MAX),
|
||||
("2026-09-09", i64::MAX),
|
||||
]
|
||||
.map(|(date, tokens)| AccountTokenUsageDailyBucket {
|
||||
start_date: date.into(),
|
||||
tokens,
|
||||
});
|
||||
let values = daily_values(&buckets, today);
|
||||
let mut expected = vec![0; CELL_COUNT];
|
||||
let index = (today - chart_start(today)).num_days() as usize;
|
||||
expected[index - 1] = i64::MAX;
|
||||
expected[index] = i64::MAX;
|
||||
assert_eq!(values, expected);
|
||||
for view in [
|
||||
TokenActivityView::Daily,
|
||||
TokenActivityView::Weekly,
|
||||
TokenActivityView::Cumulative,
|
||||
] {
|
||||
let levels = levels_for_view(&values, view);
|
||||
assert!(levels.iter().all(|level| *level <= 4));
|
||||
assert!(levels.contains(&4));
|
||||
assert!(!chart_lines(view, &buckets, today, /*width*/ 110).is_empty());
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ impl AnalyticsView {
|
||||
}
|
||||
let unit = match section {
|
||||
Section::Usage if history.unit == AccountAnalyticsUnit::Tokens => "tokens",
|
||||
Section::Summary => "tokens",
|
||||
Section::Activity => "messages",
|
||||
Section::Plugins => "calls",
|
||||
Section::Skills => "uses",
|
||||
@@ -107,6 +108,12 @@ impl AnalyticsView {
|
||||
} else {
|
||||
data::amount
|
||||
};
|
||||
let total: f64 = history.data.iter().map(|day| day.total).sum();
|
||||
let formatted_total = if total.abs() >= 1_000.0 {
|
||||
data::compact_amount(total)
|
||||
} else {
|
||||
format_amount(total)
|
||||
};
|
||||
let mut lines = vec![if relative {
|
||||
"Total usage · relative units"
|
||||
.set_style(secondary_style())
|
||||
@@ -114,7 +121,7 @@ impl AnalyticsView {
|
||||
} else {
|
||||
number(format!(
|
||||
"{} {}{unit}",
|
||||
format_amount(history.data.iter().map(|day| day.total).sum()),
|
||||
formatted_total,
|
||||
if section == Section::Activity {
|
||||
"reported "
|
||||
} else {
|
||||
@@ -194,9 +201,11 @@ impl AnalyticsView {
|
||||
Section::Plugins => "Plugin",
|
||||
Section::Skills => "Skill",
|
||||
Section::Chats => "Chat",
|
||||
Section::Usage | Section::Credits | Section::Activity | Section::Plan => {
|
||||
self.group_label(section, self.sections[section].group)
|
||||
}
|
||||
Section::Summary
|
||||
| Section::Usage
|
||||
| Section::Credits
|
||||
| Section::Activity
|
||||
| Section::Plan => self.group_label(section, self.sections[section].group),
|
||||
}
|
||||
.bold()
|
||||
.into(),
|
||||
|
||||
@@ -15,7 +15,7 @@ fn chats_range_controls_and_visible_rows() {
|
||||
})
|
||||
.collect();
|
||||
view.chats = Load::Ready(chats);
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
let before = (
|
||||
view.ranges,
|
||||
view.sections.0.each_ref().map(|state| state.cursor),
|
||||
@@ -48,7 +48,7 @@ fn chats_range_controls_and_visible_rows() {
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
assert_eq!(view.ranges, [0; 3]);
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
assert!(screen(&mut view, /*width*/ 110, /*height*/ 24).contains("[30d]"));
|
||||
@@ -82,7 +82,7 @@ fn chats_breakdown_ranks_nonzero_groups_and_reflows() {
|
||||
.map(|group| group.estimated_usage_credits_micros)
|
||||
.sum();
|
||||
view.chats = Load::Ready(chats);
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
press(&mut view, KeyCode::Enter);
|
||||
let mut screens = Vec::new();
|
||||
for width in [110, 58] {
|
||||
@@ -116,7 +116,7 @@ fn chats_long_title_keeps_detail_indent_and_counts_missing_estimates() {
|
||||
chats.rows[1].usage = None;
|
||||
chats.rows[2].usage = None;
|
||||
view.chats = Load::Ready(chats);
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
press(&mut view, KeyCode::Enter);
|
||||
let output = screen(&mut view, /*width*/ 110, /*height*/ 30);
|
||||
assert!(output.contains("2 chat estimates unavailable"));
|
||||
|
||||
@@ -44,11 +44,7 @@ impl Live {
|
||||
|
||||
pub(super) fn account_label(&self) -> Option<String> {
|
||||
let session = self.session.get()?;
|
||||
let account = session.backend.account();
|
||||
Some(match &account.email {
|
||||
Some(email) => format!("{email} · {}", account.id),
|
||||
None => account.id.clone(),
|
||||
})
|
||||
session.backend.account().email.clone()
|
||||
}
|
||||
|
||||
pub(super) fn credit_groups(&self) -> &[usize] {
|
||||
@@ -262,6 +258,10 @@ pub(super) fn request_error(error: RequestError) -> String {
|
||||
#[path = "client_tests.rs"]
|
||||
pub(super) mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "connection_tests.rs"]
|
||||
mod connection_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "account_plan_tests.rs"]
|
||||
mod account_plan_tests;
|
||||
|
||||
@@ -147,7 +147,7 @@ async fn analytics_requests_use_reloaded_credentials_for_the_same_identity() {
|
||||
let authenticated = &session.backend;
|
||||
assert_eq!(
|
||||
live.account_label().as_deref(),
|
||||
Some("analytics@example.test · account-a")
|
||||
Some("analytics@example.test")
|
||||
);
|
||||
let auth_path = home.path().join("auth.json");
|
||||
let mut auth: serde_json::Value =
|
||||
@@ -846,3 +846,6 @@ async fn plan_history_rejects_account_change_during_response() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[path = "profile_identity_tests.rs"]
|
||||
mod profile_identity;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//! Large report totals and axes stay compact while selected-day details retain precision.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn report_headlines_and_axes_compact_large_values() {
|
||||
let mut screenshots = Vec::new();
|
||||
for (section, unit, total, headline) in [
|
||||
(
|
||||
Section::Usage,
|
||||
models::AccountAnalyticsUnit::Tokens,
|
||||
12_280_365_226.0,
|
||||
"12.3B tokens",
|
||||
),
|
||||
(
|
||||
Section::Plugins,
|
||||
models::AccountAnalyticsUnit::Count,
|
||||
9_749.0,
|
||||
"9.7K calls",
|
||||
),
|
||||
(
|
||||
Section::Skills,
|
||||
models::AccountAnalyticsUnit::Count,
|
||||
2_901.0,
|
||||
"2.9K uses",
|
||||
),
|
||||
] {
|
||||
let mut view = fixture::view(models::AccountKind::Enterprise);
|
||||
view.section = section;
|
||||
view.sections[section].history = Load::Ready(models::AccountAnalyticsHistory {
|
||||
unit,
|
||||
updated_at: None,
|
||||
data: vec![models::AccountAnalyticsDay {
|
||||
date: view.end_date,
|
||||
total,
|
||||
values: vec![models::AccountAnalyticsValue {
|
||||
key: "test".into(),
|
||||
label: "Example".into(),
|
||||
value: total,
|
||||
}],
|
||||
}],
|
||||
});
|
||||
let rendered = screen(&mut view, /*width*/ 100, /*height*/ 30);
|
||||
assert!(rendered.contains(headline));
|
||||
assert!(rendered.contains(&data::amount(total)));
|
||||
screenshots.push(rendered);
|
||||
}
|
||||
insta::assert_snapshot!(screenshots.join("\n"));
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
//! Exercise local Analytics plan discovery and report recovery while connected to an app-server.
|
||||
|
||||
use super::tests::live;
|
||||
use codex_app_server_client::AppServerRequestHandle;
|
||||
use codex_app_server_client::RemoteAppServerClient;
|
||||
use codex_app_server_client::RemoteAppServerConnectArgs;
|
||||
use codex_app_server_client::RemoteAppServerEndpoint;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use wiremock::Mock;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::header;
|
||||
use wiremock::matchers::method;
|
||||
|
||||
async fn read(socket: &mut WebSocketStream<TcpStream>) -> Value {
|
||||
let frame = socket.next().await.unwrap().unwrap();
|
||||
serde_json::from_str(frame.to_text().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
async fn remote() -> (RemoteAppServerClient, tokio::task::JoinHandle<()>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let server = tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let mut socket = tokio_tungstenite::accept_async(stream).await.unwrap();
|
||||
let initialize = read(&mut socket).await;
|
||||
assert_eq!(initialize["method"], "initialize");
|
||||
socket
|
||||
.send(Message::Text(
|
||||
json!({"id": initialize["id"], "result": {
|
||||
"userAgent": "analytics-test", "codexHome": "/server/.codex",
|
||||
}})
|
||||
.to_string()
|
||||
.into(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(read(&mut socket).await["method"], "initialized");
|
||||
// Analytics uses the local account without requesting connected-server account data.
|
||||
assert!(socket.next().await.unwrap().unwrap().is_close());
|
||||
});
|
||||
let client = RemoteAppServerClient::connect(RemoteAppServerConnectArgs {
|
||||
endpoint: RemoteAppServerEndpoint::WebSocket {
|
||||
websocket_url: format!("ws://{address}"),
|
||||
auth_token: None,
|
||||
},
|
||||
client_name: "analytics-test".into(),
|
||||
client_version: "0.0.0-test".into(),
|
||||
experimental_api: true,
|
||||
mcp_server_openai_form_elicitation: false,
|
||||
opt_out_notification_methods: Vec::new(),
|
||||
channel_capacity: 8,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
(client, server)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn local_reports_share_an_account_and_recover_profile_errors() {
|
||||
use crate::analytics::AnalyticsView;
|
||||
use crate::analytics::data::Load;
|
||||
use crate::analytics::sections::Section;
|
||||
|
||||
let http = crate::analytics::test_support::server().await;
|
||||
let (_home, local) = live(&http, "enterprise").await;
|
||||
let (client, server) = remote().await;
|
||||
let failed_profile = Mock::given(method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/profiles/me"))
|
||||
.and(header("chatgpt-account-id", "account-a"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 503))
|
||||
.expect(/*r*/ 1)
|
||||
.mount_as_scoped(&http)
|
||||
.await;
|
||||
let mut view = AnalyticsView::new(crate::keymap::RuntimeKeymap::defaults().list);
|
||||
view.open(
|
||||
AppServerRequestHandle::Remote(client.request_handle()),
|
||||
crate::tui::FrameRequester::test_dummy(),
|
||||
Vec::new(),
|
||||
std::sync::Arc::clone(&local.config),
|
||||
);
|
||||
crate::analytics::test_support::settle(&mut view).await;
|
||||
assert_eq!(
|
||||
view.visible_sections(),
|
||||
&[
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
Section::Skills,
|
||||
]
|
||||
);
|
||||
assert!(matches!(view.profile, Load::Error(_)));
|
||||
for section in view.visible_sections() {
|
||||
if section.report().is_some() {
|
||||
assert!(view.sections[*section].history.ready().is_some());
|
||||
}
|
||||
}
|
||||
view.select_summary(/*view*/ None);
|
||||
view.end_date = "2026-09-09".parse().unwrap();
|
||||
let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(
|
||||
/*width*/ 100, /*height*/ 48,
|
||||
))
|
||||
.unwrap();
|
||||
terminal
|
||||
.draw(|frame| view.render(frame.area(), frame.buffer_mut()))
|
||||
.unwrap();
|
||||
insta::assert_snapshot!("local_profile_error", terminal.backend().to_string());
|
||||
drop(failed_profile);
|
||||
|
||||
let profile = json!({
|
||||
"profile":{"display_name":"Local account","username":"local-user"},
|
||||
"stats":{"lifetime_tokens":12345,"peak_daily_tokens":12345,
|
||||
"daily_usage_buckets":[{"start_date":"2026-09-09","tokens":12345}]}
|
||||
});
|
||||
Mock::given(method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/profiles/me"))
|
||||
.and(header("chatgpt-account-id", "account-a"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 200).set_body_json(profile.clone()))
|
||||
.expect(/*r*/ 1)
|
||||
.mount(&http)
|
||||
.await;
|
||||
view.refresh();
|
||||
crate::analytics::test_support::settle(&mut view).await;
|
||||
assert_eq!(
|
||||
view.profile.ready(),
|
||||
Some(&serde_json::from_value(profile).unwrap())
|
||||
);
|
||||
view.end_date = "2026-09-09".parse().unwrap();
|
||||
terminal
|
||||
.draw(|frame| view.render(frame.area(), frame.buffer_mut()))
|
||||
.unwrap();
|
||||
insta::assert_snapshot!("local_profile_recovered", terminal.backend().to_string());
|
||||
for request in http.received_requests().await.unwrap() {
|
||||
assert_eq!(
|
||||
request.headers.get("chatgpt-account-id").unwrap(),
|
||||
"account-a"
|
||||
);
|
||||
}
|
||||
view.cancel_loads();
|
||||
client.shutdown().await.unwrap();
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn account_lookup_failure_recovers_on_refresh_with_the_server_plan() {
|
||||
use crate::analytics::AnalyticsView;
|
||||
use crate::analytics::data::Load;
|
||||
use crate::analytics::sections::Section;
|
||||
|
||||
let http = crate::analytics::test_support::server().await;
|
||||
let (_home, local) = live(&http, "plus").await;
|
||||
let (client, server) = remote().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/accounts/check"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 503))
|
||||
.with_priority(/*p*/ 1)
|
||||
.expect(/*r*/ 1)
|
||||
.up_to_n_times(/*n*/ 1)
|
||||
.mount(&http)
|
||||
.await;
|
||||
let mut view = AnalyticsView::new(crate::keymap::RuntimeKeymap::defaults().list);
|
||||
view.open(
|
||||
AppServerRequestHandle::Remote(client.request_handle()),
|
||||
crate::tui::FrameRequester::test_dummy(),
|
||||
Vec::new(),
|
||||
std::sync::Arc::clone(&local.config),
|
||||
);
|
||||
crate::analytics::test_support::settle(&mut view).await;
|
||||
assert!(matches!(view.account, Load::Error(_)));
|
||||
assert_eq!(
|
||||
http.received_requests()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|request| request.url.path())
|
||||
.collect::<Vec<_>>(),
|
||||
["/backend-api/wham/accounts/check"]
|
||||
);
|
||||
view.end_date = "2026-09-09".parse().unwrap();
|
||||
let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(
|
||||
/*width*/ 100, /*height*/ 40,
|
||||
))
|
||||
.unwrap();
|
||||
terminal
|
||||
.draw(|frame| view.render(frame.area(), frame.buffer_mut()))
|
||||
.unwrap();
|
||||
insta::assert_snapshot!("account_plan_error", terminal.backend().to_string());
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/accounts/check"))
|
||||
.and(header("chatgpt-account-id", "account-a"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 200).set_body_json(json!({
|
||||
"accounts": [{"id": "account-a", "plan_type": "enterprise"}]
|
||||
})))
|
||||
.with_priority(/*p*/ 1)
|
||||
.expect(/*r*/ 1)
|
||||
.mount(&http)
|
||||
.await;
|
||||
Mock::given(method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/profiles/me"))
|
||||
.respond_with(ResponseTemplate::new(/*s*/ 200).set_body_json(json!({"stats": {}})))
|
||||
.mount(&http)
|
||||
.await;
|
||||
view.refresh();
|
||||
crate::analytics::test_support::settle(&mut view).await;
|
||||
assert_eq!(
|
||||
view.visible_sections(),
|
||||
&[
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
Section::Skills,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.account.ready(),
|
||||
Some(&codex_protocol::account::PlanType::Enterprise)
|
||||
);
|
||||
for section in view.visible_sections() {
|
||||
if section.report().is_some() {
|
||||
assert!(view.sections[*section].history.ready().is_some());
|
||||
}
|
||||
}
|
||||
http.verify().await;
|
||||
view.cancel_loads();
|
||||
client.shutdown().await.unwrap();
|
||||
server.await.unwrap();
|
||||
}
|
||||
@@ -43,7 +43,7 @@ impl AnalyticsView {
|
||||
secondary_style().dim()
|
||||
};
|
||||
let mut content = Vec::new();
|
||||
if !matches!(section, Section::Chats | Section::Plan) {
|
||||
if !matches!(section, Section::Chats | Section::Plan | Section::Summary) {
|
||||
content.push(
|
||||
format!(
|
||||
"{}d{}",
|
||||
@@ -100,6 +100,7 @@ impl AnalyticsView {
|
||||
));
|
||||
} else {
|
||||
match section {
|
||||
Section::Summary => content.extend(self.summary_lines(inner_width)),
|
||||
Section::Usage
|
||||
| Section::Plugins
|
||||
| Section::Credits
|
||||
|
||||
@@ -12,7 +12,10 @@ fn dashboard_focus_uses_marker_and_default_foreground_title() {
|
||||
for section in [Section::Usage, Section::Activity] {
|
||||
view.section = section;
|
||||
let (lines, _) = view.dashboard_lines(/*width*/ 140, /*height*/ 56);
|
||||
let title = &lines[0];
|
||||
let title = lines
|
||||
.iter()
|
||||
.find(|line| line.spans.iter().any(|span| span.content == "▸"))
|
||||
.unwrap();
|
||||
let marker = title.spans.iter().find(|span| span.content == "▸").unwrap();
|
||||
assert_eq!(marker.style, accent_style());
|
||||
let focused_title = title
|
||||
@@ -32,8 +35,9 @@ fn dashboard_cards_align_and_keep_stable_summary_heights() {
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
let wide = screen(&mut view, /*width*/ 144, /*height*/ 64);
|
||||
for (left, right) in [
|
||||
("1 Total usage history", "2 Messages"),
|
||||
("3 Plugins called", "4 Skills used"),
|
||||
("1 Summary", "2 Total usage history"),
|
||||
("3 Messages", "4 Plugins called"),
|
||||
("5 Skills used", "6 Top chats"),
|
||||
] {
|
||||
assert!(
|
||||
wide.lines()
|
||||
@@ -64,7 +68,7 @@ fn dashboard_cards_align_and_keep_stable_summary_heights() {
|
||||
);
|
||||
}
|
||||
let narrow = screen(&mut view, /*width*/ 72, /*height*/ 48);
|
||||
assert!(narrow.contains("1 Total usage history"));
|
||||
assert!(narrow.contains("1 Summary"));
|
||||
let context = (
|
||||
view.section,
|
||||
view.sections.0.each_ref().map(|state| state.cursor),
|
||||
|
||||
@@ -146,6 +146,27 @@ pub(super) fn amount(value: f64) -> String {
|
||||
grouped
|
||||
}
|
||||
|
||||
/// Compact headline and axis labels to one decimal, preserving small signed adjustments.
|
||||
pub(super) fn compact_amount(value: f64) -> String {
|
||||
if value.abs() < 1_000.0 || !value.is_finite() {
|
||||
return amount(value);
|
||||
}
|
||||
let units = ["K", "M", "B", "T"];
|
||||
let mut scaled = value / 1_000.0;
|
||||
let mut unit = 0;
|
||||
// Promote before rounding would produce a label such as 1000K.
|
||||
while scaled.abs() >= 999.95 && unit + 1 < units.len() {
|
||||
scaled /= 1_000.0;
|
||||
unit += 1;
|
||||
}
|
||||
let rounded = format!("{:.1}", (scaled * 10.0).round() / 10.0);
|
||||
format!(
|
||||
"{}{}",
|
||||
rounded.trim_end_matches('0').trim_end_matches('.'),
|
||||
units[unit]
|
||||
)
|
||||
}
|
||||
|
||||
/// Align ordinary credit amounts to two decimals without hiding tiny adjustments.
|
||||
pub(super) fn credit_amount(value: f64) -> String {
|
||||
let formatted = amount(value);
|
||||
|
||||
@@ -94,3 +94,39 @@ async fn extended_chat_load_budget_still_times_out_and_can_be_cancelled() {
|
||||
load.poll();
|
||||
assert_eq!(load.message(), Some("Request timed out. Press R to retry."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_counts_round_promote_units_and_preserve_small_adjustments() {
|
||||
let values = [
|
||||
0.0,
|
||||
959.0,
|
||||
1_000.0,
|
||||
9_749.0,
|
||||
2_901.0,
|
||||
12_280_365_226.0,
|
||||
142_300_000_000.0,
|
||||
999_999.0,
|
||||
999_999_999.0,
|
||||
-1_250.0,
|
||||
-0.000004,
|
||||
1_200_000_000_000.0,
|
||||
];
|
||||
assert_eq!(
|
||||
values.map(compact_amount),
|
||||
[
|
||||
"0",
|
||||
"959",
|
||||
"1K",
|
||||
"9.7K",
|
||||
"2.9K",
|
||||
"12.3B",
|
||||
"142.3B",
|
||||
"1M",
|
||||
"1B",
|
||||
"-1.3K",
|
||||
"-0.000004",
|
||||
"1.2T"
|
||||
]
|
||||
.map(str::to_string)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,10 @@ impl AnalyticsView {
|
||||
}),
|
||||
);
|
||||
}
|
||||
if section == Section::Plan {
|
||||
if section == Section::Summary {
|
||||
selection = 0..1;
|
||||
lines.extend(self.summary_lines(width));
|
||||
} else if section == Section::Plan {
|
||||
let (plan, detail) = self.plan_lines(width);
|
||||
selection = lines.len() + detail.start..lines.len() + detail.end;
|
||||
lines.extend(plan);
|
||||
|
||||
@@ -8,6 +8,7 @@ mod painting;
|
||||
use self::layout::Layout;
|
||||
use self::painting::Band;
|
||||
use super::data;
|
||||
use super::data::compact_amount as tick;
|
||||
use super::render::parts;
|
||||
use crate::analytics::models::AccountAnalyticsDay;
|
||||
use crate::analytics::models::AccountAnalyticsUnit;
|
||||
@@ -110,8 +111,7 @@ pub(super) fn chart(
|
||||
&[Band::Positive][..]
|
||||
};
|
||||
let peak = axis_max(peak(days, categories), unit);
|
||||
let label_width =
|
||||
tick(peak, unit).width().max(tick(peak / 2.0, unit).width()) + bands.len() - 1 + 2;
|
||||
let label_width = tick(peak).width().max(tick(peak / 2.0).width()) + bands.len() - 1 + 2;
|
||||
let layout = Layout::new(days.len(), cursor, width, height, label_width, bands);
|
||||
let renderer = Renderer {
|
||||
days,
|
||||
@@ -164,20 +164,13 @@ struct Renderer<'a> {
|
||||
fn amount(value: f64, unit: AccountAnalyticsUnit) -> String {
|
||||
match unit {
|
||||
AccountAnalyticsUnit::RelativeUsage => data::amount(value),
|
||||
AccountAnalyticsUnit::Count | AccountAnalyticsUnit::Tokens => data::amount(value.round()),
|
||||
AccountAnalyticsUnit::Count | AccountAnalyticsUnit::Tokens => {
|
||||
data::compact_amount(value.round())
|
||||
}
|
||||
AccountAnalyticsUnit::Credits => data::credit_amount(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(value: f64, unit: AccountAnalyticsUnit) -> String {
|
||||
match unit {
|
||||
AccountAnalyticsUnit::Credits => data::amount(value),
|
||||
AccountAnalyticsUnit::RelativeUsage
|
||||
| AccountAnalyticsUnit::Count
|
||||
| AccountAnalyticsUnit::Tokens => amount(value, unit),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "plot_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Renderer<'_> {
|
||||
ticks.push((baseline + height, -self.peak));
|
||||
}
|
||||
for (y, value) in ticks {
|
||||
let label = tick(value, self.unit);
|
||||
let label = tick(value);
|
||||
let x = axis_width.saturating_sub(label.len() + 1);
|
||||
buf.set_line(
|
||||
x as u16,
|
||||
|
||||
@@ -311,14 +311,21 @@ fn partial_cells_use_the_midpoint_and_descend_from_the_baseline() {
|
||||
|
||||
#[test]
|
||||
fn oversized_axes_are_hidden_instead_of_truncated() {
|
||||
let day = AccountAnalyticsDay {
|
||||
date: "2026-01-15".parse().unwrap(),
|
||||
total: 100_000_000.0,
|
||||
values: vec![value("a", /*value*/ 100_000_000.0)],
|
||||
};
|
||||
let days = [(day.date, Some(&day))];
|
||||
let mut snapshots = Vec::new();
|
||||
for width in [8, 30, 35, 44] {
|
||||
for (width, total) in [
|
||||
(8, 100_000_000.0),
|
||||
(30, 100_000_000.0),
|
||||
(35, 100_000_000.0),
|
||||
(44, 100_000_000.0),
|
||||
(30, 1e30),
|
||||
] {
|
||||
let day = AccountAnalyticsDay {
|
||||
date: "2026-01-15".parse().unwrap(),
|
||||
total,
|
||||
values: vec![value("a", total)],
|
||||
};
|
||||
let days = [(day.date, Some(&day))];
|
||||
|
||||
let rendered = chart(
|
||||
&days,
|
||||
&day.values,
|
||||
@@ -328,9 +335,9 @@ fn oversized_axes_are_hidden_instead_of_truncated() {
|
||||
AccountAnalyticsUnit::Tokens,
|
||||
);
|
||||
let baseline = rendered.lines[5].to_string();
|
||||
assert_eq!(baseline.starts_with('─'), width < 36);
|
||||
assert_eq!(baseline.starts_with('─'), width < 30 || total == 1e30);
|
||||
if width == 8 {
|
||||
assert!(rendered.lines[0].to_string().trim().is_empty());
|
||||
assert!(rendered.lines[0].to_string().contains("100M"));
|
||||
}
|
||||
snapshots.push(format!("width={width}\n{}", display(&rendered)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//! In-flight profile responses cannot expose data after the active account changes.
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_rejects_account_change_during_success_or_error_response() {
|
||||
let server = MockServer::start().await;
|
||||
for status in [200, 404, 503] {
|
||||
server.reset().await;
|
||||
let (home, live) = live(&server, "plus").await;
|
||||
let home_path = home.path().to_path_buf();
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/backend-api/wham/profiles/me"))
|
||||
.and(header("chatgpt-account-id", "account-a"))
|
||||
.respond_with(move |_: &wiremock::Request| {
|
||||
sign_in(&home_path, "account-a", "user-b", "plus");
|
||||
ResponseTemplate::new(status)
|
||||
.set_body_json(json!({"stats":{"lifetime_tokens":123}}))
|
||||
})
|
||||
.expect(/*r*/ 1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
assert_eq!(
|
||||
live.profile().await.err().unwrap(),
|
||||
"Account changed. Press R to refresh Analytics."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,8 +92,10 @@ impl AnalyticsView {
|
||||
pub(super) fn render(&mut self, area: Rect, buf: &mut Buffer) {
|
||||
self.poll_reports();
|
||||
let width = usize::from(area.width.saturating_sub(/*rhs*/ 4)).max(/*other*/ 1);
|
||||
let show_range = !matches!(self.section, Section::Chats | Section::Plan)
|
||||
&& !self.visible_sections().is_empty();
|
||||
let show_range = !matches!(
|
||||
self.section,
|
||||
Section::Chats | Section::Plan | Section::Summary
|
||||
) && !self.visible_sections().is_empty();
|
||||
let hint = |action| {
|
||||
self.keymap
|
||||
.primary_hint(action)
|
||||
@@ -104,15 +106,26 @@ impl AnalyticsView {
|
||||
format!("R refresh · {} back · q close", hint(ListAction::Cancel))
|
||||
} else if self.group_picker.is_some() {
|
||||
format!(
|
||||
"{}/{} group · {} select · {} cancel",
|
||||
"{}/{} {} · {} select · {} cancel",
|
||||
hint(ListAction::MoveUp),
|
||||
hint(ListAction::MoveDown),
|
||||
if self.section == Section::Summary {
|
||||
"view"
|
||||
} else {
|
||||
"group"
|
||||
},
|
||||
hint(ListAction::Accept),
|
||||
hint(ListAction::Cancel)
|
||||
)
|
||||
} else {
|
||||
let navigation = if !self.zoomed {
|
||||
format!("{}/z maximize", hint(ListAction::Accept))
|
||||
} else if self.section == Section::Summary {
|
||||
format!(
|
||||
"{}/{} scroll",
|
||||
hint(ListAction::MoveUp),
|
||||
hint(ListAction::MoveDown)
|
||||
)
|
||||
} else if self.section == Section::Plan {
|
||||
format!(
|
||||
"{}/{} window · {}/{} period · {} details",
|
||||
@@ -152,6 +165,8 @@ impl AnalyticsView {
|
||||
if show_range { "r 7/30d · " } else { "" },
|
||||
if self.group_options().len() < 2 {
|
||||
""
|
||||
} else if self.section == Section::Summary {
|
||||
"g view · "
|
||||
} else {
|
||||
"g group · "
|
||||
},
|
||||
@@ -275,7 +290,12 @@ impl AnalyticsView {
|
||||
.account
|
||||
.message()
|
||||
.unwrap_or("Analytics is not available for this account type.");
|
||||
(vec![Line::from(message.to_string())], 0..1)
|
||||
let lines = textwrap::wrap(message, width)
|
||||
.into_iter()
|
||||
.map(|line| Line::from(line.into_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
let selection = 0..lines.len();
|
||||
(lines, selection)
|
||||
} else if self.zoomed {
|
||||
let chart_height =
|
||||
(usize::from(body.height) / 5).clamp(/*min*/ 4, /*max*/ 10);
|
||||
@@ -289,12 +309,24 @@ impl AnalyticsView {
|
||||
panel = self.panel(self.section, width, height);
|
||||
}
|
||||
}
|
||||
let center_summary = self.section == Section::Summary
|
||||
&& self.profile.ready().is_some()
|
||||
&& self.group_picker.is_none();
|
||||
if center_summary {
|
||||
// Keep the section heading and rule fixed; center the profile below them.
|
||||
let padding = usize::from(body.height).saturating_sub(panel.lines.len()) / 2;
|
||||
panel
|
||||
.lines
|
||||
.splice(2..2, std::iter::repeat_n(Line::default(), padding));
|
||||
}
|
||||
let selection = if panel.lines.len() <= usize::from(body.height) {
|
||||
0..panel.lines.len()
|
||||
} else {
|
||||
panel.selection
|
||||
};
|
||||
panel.lines.push(Line::default());
|
||||
if !center_summary {
|
||||
panel.lines.push(Line::default());
|
||||
}
|
||||
(panel.lines, selection)
|
||||
} else {
|
||||
self.dashboard_lines(width, usize::from(body.height))
|
||||
|
||||
@@ -16,6 +16,7 @@ pub(super) enum Section {
|
||||
Activity,
|
||||
Skills,
|
||||
Plan,
|
||||
Summary,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
@@ -25,7 +26,7 @@ impl Section {
|
||||
Self::Usage => Some(Report::Usage),
|
||||
Self::Plugins => Some(Report::Plugins),
|
||||
Self::Credits => Some(Report::Credits),
|
||||
Self::Chats | Self::Plan => None,
|
||||
Self::Chats | Self::Plan | Self::Summary => None,
|
||||
Self::Activity => Some(Report::Messages),
|
||||
Self::Skills => Some(Report::Skills),
|
||||
}
|
||||
@@ -33,7 +34,7 @@ impl Section {
|
||||
}
|
||||
|
||||
/// Section-only indexing keeps report identity separate from array positions.
|
||||
pub(super) struct SectionStates(pub(super) [SectionState; 7]);
|
||||
pub(super) struct SectionStates(pub(super) [SectionState; 8]);
|
||||
|
||||
impl std::ops::Index<Section> for SectionStates {
|
||||
type Output = SectionState;
|
||||
@@ -82,6 +83,7 @@ impl AnalyticsView {
|
||||
}
|
||||
self.chats.poll();
|
||||
self.tasks.poll();
|
||||
self.profile.poll();
|
||||
self.plan.poll();
|
||||
self.account.poll();
|
||||
self.start_reports();
|
||||
@@ -112,13 +114,20 @@ impl AnalyticsView {
|
||||
pub(super) fn range_group(&self, section: Section) -> RangeGroup {
|
||||
match section {
|
||||
Section::Usage if self.business() => RangeGroup::Activity,
|
||||
Section::Usage | Section::Credits | Section::Chats | Section::Plan => RangeGroup::Usage,
|
||||
Section::Usage
|
||||
| Section::Credits
|
||||
| Section::Chats
|
||||
| Section::Plan
|
||||
| Section::Summary => RangeGroup::Usage,
|
||||
Section::Activity => RangeGroup::Activity,
|
||||
Section::Plugins | Section::Skills => RangeGroup::Tools,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn group_label(&self, section: Section, group: usize) -> &'static str {
|
||||
if section == Section::Summary {
|
||||
return super::summary::VIEWS[group].label();
|
||||
}
|
||||
if group == 0
|
||||
&& (section == Section::Credits
|
||||
|| (section == Section::Usage && !self.consumer_attribution()))
|
||||
@@ -146,6 +155,7 @@ impl AnalyticsView {
|
||||
pub(super) fn visible_sections(&self) -> &'static [Section] {
|
||||
match self.account_kind() {
|
||||
Some(AccountKind::Consumer) if self.plan.enabled => &[
|
||||
Section::Summary,
|
||||
Section::Usage,
|
||||
Section::Plan,
|
||||
Section::Activity,
|
||||
@@ -154,6 +164,7 @@ impl AnalyticsView {
|
||||
Section::Chats,
|
||||
],
|
||||
Some(AccountKind::Consumer) => &[
|
||||
Section::Summary,
|
||||
Section::Usage,
|
||||
Section::Activity,
|
||||
Section::Plugins,
|
||||
@@ -164,6 +175,7 @@ impl AnalyticsView {
|
||||
if super::models::thread_usage_supported(self.account.ready().copied()) =>
|
||||
{
|
||||
&[
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
@@ -172,12 +184,14 @@ impl AnalyticsView {
|
||||
]
|
||||
}
|
||||
Some(AccountKind::Business | AccountKind::Enterprise) => &[
|
||||
Section::Summary,
|
||||
Section::Credits,
|
||||
Section::Usage,
|
||||
Section::Plugins,
|
||||
Section::Skills,
|
||||
],
|
||||
Some(AccountKind::Unknown) | None => &[],
|
||||
Some(AccountKind::Unknown) => &[Section::Summary],
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,13 +220,15 @@ impl AnalyticsView {
|
||||
usage_groups[0]
|
||||
};
|
||||
}
|
||||
if let Some(live) = &self.live
|
||||
if visible.contains(&Section::Credits)
|
||||
&& let Some(live) = &self.live
|
||||
&& !live
|
||||
.credit_groups()
|
||||
.contains(&self.sections[Section::Credits].group)
|
||||
{
|
||||
self.sections[Section::Credits].group = live.credit_groups()[0];
|
||||
}
|
||||
self.load_summary();
|
||||
for section in visible {
|
||||
if section.report().is_some() {
|
||||
self.load_report(*section);
|
||||
@@ -259,7 +275,7 @@ impl AnalyticsView {
|
||||
let index = self.range_group(self.section);
|
||||
self.ranges[index as usize] ^= 1;
|
||||
for section in self.visible_sections() {
|
||||
if matches!(section, Section::Chats | Section::Plan)
|
||||
if matches!(section, Section::Chats | Section::Plan | Section::Summary)
|
||||
|| self.range_group(*section) != index
|
||||
{
|
||||
continue;
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: tui/src/analytics/connection_tests.rs
|
||||
assertion_line: 189
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Couldn't load account plan. Press R to retry Analytics. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" R refresh · esc back · q close "
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: tui/src/analytics/connection_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" Report request failed. Press R to retry. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: tui/src/analytics/connection_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Local account "
|
||||
" @local-user "
|
||||
" Enterprise "
|
||||
" "
|
||||
" 12.3K 12.3K — — — "
|
||||
" Lifetime tokens Peak tokens Longest chat Current streak Longest streak "
|
||||
" "
|
||||
" Token activity [Daily] Weekly Cumulative "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Nov Dec Jan Feb Mar Apr May Jun Jul Aug "
|
||||
" Su □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Mo □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Tu □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" We □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ "
|
||||
" Th □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Fr □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Sa □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" "
|
||||
" Less □ ■ ■ ■ ■ More "
|
||||
" "
|
||||
" Activity insights Most used plugins and skills "
|
||||
" "
|
||||
" Fast Mode — Plugin and skill usage unavailable. "
|
||||
" Most used reasoning — "
|
||||
" Skills explored — "
|
||||
" Total skills used — "
|
||||
" Total chats — "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
source: tui/src/analytics/client_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
" Analytics · Live account · analytics@example.test · account-a "
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
|
||||
+5
-6
@@ -1,11 +1,10 @@
|
||||
---
|
||||
source: tui/src/analytics/plan_tests.rs
|
||||
assertion_line: 56
|
||||
expression: "output.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history [2 Plan usage history] 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" 1 Summary 2 Total usage history [3 Plan usage history] 4 Messages 5 Plugins called 6 Skills used 7 Top chats "
|
||||
" "
|
||||
" ▎ Plan usage history By feature "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -31,13 +30,13 @@ expression: "output.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ←/→ window · ↑/↓ period · enter details "
|
||||
" tab/1–7 section · z dashboard · ←/→ window · ↑/↓ period · enter details "
|
||||
" g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" Total usage history [Plan usage history] Messages "
|
||||
" Plugins called Skills used Top chats "
|
||||
" Summary Total usage history [Plan usage history] "
|
||||
" Messages Plugins called Skills used Top chats "
|
||||
" "
|
||||
" ▎ Plan usage history By feature "
|
||||
" ────────────────────────────────────────────────────── "
|
||||
@@ -60,7 +59,7 @@ expression: "output.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ←/→ window · ↑/↓ "
|
||||
" tab/1–7 section · z dashboard · ←/→ window · ↑/↓ "
|
||||
" period · enter details "
|
||||
" g group · R refresh · pgup/pgdn scroll · esc back · q "
|
||||
" close "
|
||||
|
||||
+45
-46
@@ -1,65 +1,64 @@
|
||||
---
|
||||
source: tui/src/analytics/plan_tests.rs
|
||||
assertion_line: 210
|
||||
expression: output
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" ╭─ 1 Total usage history ────────────────────────────╮ ╭─ ▸ 2 Plan usage history ─────────────────────────────╮ "
|
||||
" ╭─ 1 Summary ────────────────────────────────────────╮ ╭─ 2 Total usage history ────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ By feature │ "
|
||||
" │ By feature │ │ › 5-hour limits │ "
|
||||
" │ Total usage · relative units │ │ │ "
|
||||
" │ 86 │ │ Latest period · 12 total 125% │ "
|
||||
" │ 100 ▁▁▁▁ ▅▅▅▅ ▅▅▅▅ ▇▇▇▇ ▆▆▆▆ ████ ▆▆▆▆ │ │ │ "
|
||||
" │ 0 ─────────────────────────────────────────────── │ │ Weekly limits │ "
|
||||
" │ ▲ │ │ │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ Latest period · 1 total Not available │ "
|
||||
" │ │ │ Some periods aren't available yet │ "
|
||||
" │ Sep 2 · 86 usage units │ │ Usage as of Sep 2 12:00 UTC │ "
|
||||
" │ ● Tasks 64.0% │ │ Available since Sep 1 00:00 UTC │ "
|
||||
" │ ● Code review 14.0% │ │ Amounts and period boundaries are approximate; │ "
|
||||
" │ │ │ recent activity may be delayed │ "
|
||||
" │ No history has been reported. │ │ 7d │ "
|
||||
" │ │ │ By feature │ "
|
||||
" │ │ │ Total usage · relative units │ "
|
||||
" │ │ │ 86 │ "
|
||||
" │ │ │ 100 ▁▁▁▁ ▅▅▅▅ ▅▅▅▅ ▇▇▇▇ ▆▆▆▆ ████ ▆▆▆▆ │ "
|
||||
" │ │ │ 0 ─────────────────────────────────────────────── │ "
|
||||
" │ │ │ ▲ │ "
|
||||
" │ │ │ 27 28 29 30 31 1 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ Sep 2 · 86 usage units │ "
|
||||
" │ │ │ ● Tasks 64.0% │ "
|
||||
" │ │ │ ● Code review 14.0% │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Messages ───────────────────────────────────────╮ ╭─ 4 Plugins called ─────────────────────────────────╮ "
|
||||
" ╭─ ▸ 3 Plan usage history ─────────────────────────────╮ ╭─ 4 Messages ───────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ By feature │ │ 7d │ "
|
||||
" │ › 5-hour limits │ │ By model │ "
|
||||
" │ │ │ 574 reported messages │ "
|
||||
" │ Latest period · 12 total 125% │ │ 103 │ "
|
||||
" │ │ │ 150 ▃▃▃▃ ▄▄▄▄ ▅▅▅▅ ▄▄▄▄ ▃▃▃▃ ▄▄▄▄ ▅▅▅▅ │ "
|
||||
" │ Weekly limits │ │ 0 ─────────────────────────────────────────────── │ "
|
||||
" │ │ │ ▲ │ "
|
||||
" │ Latest period · 1 total Not available │ │ 27 28 29 30 31 1 2 │ "
|
||||
" │ Some periods aren't available yet │ │ │ "
|
||||
" │ Usage as of Sep 2 12:00 UTC │ │ Sep 2 · 103 messages │ "
|
||||
" │ Available since Sep 1 00:00 UTC │ │ ● GPT-5.6-Sol 61 │ "
|
||||
" │ … z to maximize │ │ ● GPT-5.6-Terra 25 │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Plugins called ─────────────────────────────────╮ ╭─ 6 Skills used ────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ By model │ │ 186 calls │ "
|
||||
" │ 574 reported messages │ │ 29 │ "
|
||||
" │ 103 │ │ 40 ▆▆▆▆ ▄▄▄▄ ▅▅▅▅ ▅▅▅▅ ▄▄▄▄ ▄▄▄▄ ▅▅▅▅ │ "
|
||||
" │ 150 ▃▃▃▃ ▄▄▄▄ ▅▅▅▅ ▄▄▄▄ ▃▃▃▃ ▄▄▄▄ ▅▅▅▅ │ │ 0 ──────────────────────────────────────────────── │ "
|
||||
" │ 0 ─────────────────────────────────────────────── │ │ ▲ │ "
|
||||
" │ ▲ │ │ 27 28 29 30 31 1 2 │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ │ "
|
||||
" │ │ │ Sep 2 · 29 calls │ "
|
||||
" │ Sep 2 · 103 messages │ │ ● Slack 10 │ "
|
||||
" │ ● GPT-5.6-Sol 61 │ │ ● Figma 10 │ "
|
||||
" │ ● GPT-5.6-Terra 25 │ │ │ "
|
||||
" │ 186 calls │ │ 54 uses │ "
|
||||
" │ 29 │ │ 11 │ "
|
||||
" │ 40 ▆▆▆▆ ▄▄▄▄ ▅▅▅▅ ▅▅▅▅ ▄▄▄▄ ▄▄▄▄ ▅▅▅▅ │ │ 20 ▃▃▃▃ ▂▂▂▂ ▃▃▃▃ ▃▃▃▃ ▂▂▂▂ ▂▂▂▂ ▄▄▄▄ │ "
|
||||
" │ 0 ──────────────────────────────────────────────── │ │ 0 ──────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ 27 28 29 30 31 1 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 29 calls │ │ Sep 2 · 11 uses │ "
|
||||
" │ ● Slack 10 │ │ ● Testing 4 │ "
|
||||
" │ ● Figma 10 │ │ ● Code review 3 │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Skills used ────────────────────────────────────╮ ╭─ 6 Top chats ──────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ No history has been reported. │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ 11 │ │ │ "
|
||||
" │ 20 ▃▃▃▃ ▂▂▂▂ ▃▃▃▃ ▃▃▃▃ ▂▂▂▂ ▂▂▂▂ ▄▄▄▄ │ │ │ "
|
||||
" │ 0 ──────────────────────────────────────────────── │ │ │ "
|
||||
" │ ▲ │ │ │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 11 uses │ │ │ "
|
||||
" │ ● Testing 4 │ │ │ "
|
||||
" │ ● Code review 3 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────╯ "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" ╭─ 7 Top chats ──────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ No history has been reported. │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" tab/1–7 section · enter/z maximize "
|
||||
" g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+94
-68
@@ -3,7 +3,7 @@ source: tui/src/analytics/plot_tests.rs
|
||||
expression: "snapshots.join(\"\\n\\n\")"
|
||||
---
|
||||
width=8
|
||||
|
||||
100M
|
||||
█████
|
||||
█████
|
||||
█████
|
||||
@@ -14,24 +14,102 @@ Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
2: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
3: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
3: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0
|
||||
1: 0:0 1:1 6:0
|
||||
2: 0:0 1:1 6:0
|
||||
3: 0:0 1:1 6:0
|
||||
4: 0:0 1:1 6:0
|
||||
5: 0:2
|
||||
6: 0:0 3:3 4:0
|
||||
0: 0:0 1:1 5:0
|
||||
1: 0:0 1:2 6:0
|
||||
2: 0:0 1:2 6:0
|
||||
3: 0:0 1:2 6:0
|
||||
4: 0:0 1:2 6:0
|
||||
5: 0:3
|
||||
6: 0:0 3:1 4:0
|
||||
7: 0:4 6:0
|
||||
|
||||
width=30
|
||||
100,000,000
|
||||
████████████
|
||||
████████████
|
||||
100M
|
||||
100M ████████████
|
||||
████████████
|
||||
50M ████████████
|
||||
████████████
|
||||
0 ────────────────────────
|
||||
▲
|
||||
Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
3: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 15:1 19:0
|
||||
1: 0:0 1:2 5:0 12:3 24:0
|
||||
2: 0:0 12:3 24:0
|
||||
3: 0:0 2:2 5:0 12:3 24:0
|
||||
4: 0:0 12:3 24:0
|
||||
5: 0:0 4:2 5:0 6:2
|
||||
6: 0:0 17:1 18:0
|
||||
7: 0:0 14:4 20:0
|
||||
|
||||
width=35
|
||||
100M
|
||||
100M ████████████
|
||||
████████████
|
||||
50M ████████████
|
||||
████████████
|
||||
0 ─────────────────────────────
|
||||
▲
|
||||
Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
3: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 18:1 22:0
|
||||
1: 0:0 1:2 5:0 14:3 26:0
|
||||
2: 0:0 14:3 26:0
|
||||
3: 0:0 2:2 5:0 14:3 26:0
|
||||
4: 0:0 14:3 26:0
|
||||
5: 0:0 4:2 5:0 6:2
|
||||
6: 0:0 20:1 21:0
|
||||
7: 0:0 17:4 23:0
|
||||
|
||||
width=44
|
||||
100M
|
||||
100M ████████████
|
||||
████████████
|
||||
50M ████████████
|
||||
████████████
|
||||
0 ──────────────────────────────────────
|
||||
▲
|
||||
Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
3: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 22:1 26:0
|
||||
1: 0:0 1:2 5:0 19:3 31:0
|
||||
2: 0:0 19:3 31:0
|
||||
3: 0:0 2:2 5:0 19:3 31:0
|
||||
4: 0:0 19:3 31:0
|
||||
5: 0:0 4:2 5:0 6:2
|
||||
6: 0:0 24:1 25:0
|
||||
7: 0:0 21:4 27:0
|
||||
|
||||
width=30
|
||||
|
||||
1000000000000000000T
|
||||
▅▅▅▅▅▅▅▅▅▅▅▅
|
||||
████████████
|
||||
████████████
|
||||
──────────────────────────────
|
||||
@@ -45,63 +123,11 @@ Palette:
|
||||
3: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 9:1 20:0
|
||||
1: 0:0 9:2 21:0
|
||||
0: 0:0
|
||||
1: 0:0 4:1 24:0
|
||||
2: 0:0 9:2 21:0
|
||||
3: 0:0 9:2 21:0
|
||||
4: 0:0 9:2 21:0
|
||||
5: 0:3
|
||||
6: 0:0 14:1 15:0
|
||||
7: 0:0 11:4 17:0
|
||||
|
||||
width=35
|
||||
100,000,000
|
||||
████████████
|
||||
████████████
|
||||
████████████
|
||||
████████████
|
||||
───────────────────────────────────
|
||||
▲
|
||||
Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
3: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 12:1 23:0
|
||||
1: 0:0 11:2 23:0
|
||||
2: 0:0 11:2 23:0
|
||||
3: 0:0 11:2 23:0
|
||||
4: 0:0 11:2 23:0
|
||||
5: 0:3
|
||||
6: 0:0 17:1 18:0
|
||||
7: 0:0 14:4 20:0
|
||||
|
||||
width=44
|
||||
100,000,000
|
||||
100,000,000 ████████████
|
||||
████████████
|
||||
50,000,000 ████████████
|
||||
████████████
|
||||
0 ───────────────────────────────
|
||||
▲
|
||||
Jan 15
|
||||
|
||||
Palette:
|
||||
0: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset)
|
||||
1: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold()
|
||||
2: Style::new().fg(Color::Reset).bg(Color::Reset).underline_color(Color::Reset).dim()
|
||||
3: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset)
|
||||
4: Style::new().cyan().bg(Color::Reset).underline_color(Color::Reset).bold().underlined()
|
||||
Rows (column:palette):
|
||||
0: 0:0 23:1 34:0
|
||||
1: 0:0 1:2 12:0 22:3 34:0
|
||||
2: 0:0 22:3 34:0
|
||||
3: 0:0 2:2 12:0 22:3 34:0
|
||||
4: 0:0 22:3 34:0
|
||||
5: 0:0 11:2 12:0 13:2
|
||||
6: 0:0 28:1 29:0
|
||||
7: 0:0 25:4 31:0
|
||||
|
||||
+197
-136
@@ -1,12 +1,42 @@
|
||||
---
|
||||
source: tui/src/analytics/account_layout_tests.rs
|
||||
assertion_line: 47
|
||||
expression: "screens.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Total usage history] 2 Messages 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Total usage history] 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Total usage history By feature "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -31,13 +61,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● Images 11.6% "
|
||||
" ● Other 10.5% "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history [2 Messages] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history [3 Messages] 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Messages By model "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -62,13 +92,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● GPT-5.5 10 "
|
||||
" ● Other 7 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -93,13 +123,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -124,12 +154,12 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -155,89 +185,120 @@ expression: "screens.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" ╭─ 1 Total usage history ──────────────────────────────────────╮ ╭─ 2 Messages ─────────────────────────────────────────────────╮ "
|
||||
" ╭─ 1 Summary ──────────────────────────────────────────────────╮ ╭─ 2 Total usage history ──────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ By feature │ │ By model │ "
|
||||
" │ Total usage · relative units │ │ 574 reported messages │ "
|
||||
" │ 86 │ │ 103 │ "
|
||||
" │ 100 ▃▃▃▃▃ ▂▂▂▂▂ ▆▆▆▆▆ ▄▄▄▄▄ █████ ▅▅▅▅▅▅ │ │ 150 ▃▃▃▃▃ ▁▁▁▁▁ ▂▂▂▂▂▂ │ "
|
||||
" │ ▃▃▃▃▃ █████ █████ █████ █████ █████ ██████ │ │ ▆▆▆▆▆ █████ █████ █████ ▆▆▆▆▆ █████ ██████ │ "
|
||||
" │ 0 ───────────────────────────────────────────────────────── │ │ 0 ───────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ No history has been reported. │ │ 7d │ "
|
||||
" │ │ │ By feature │ "
|
||||
" │ │ │ Total usage · relative units │ "
|
||||
" │ │ │ 86 │ "
|
||||
" │ │ │ 100 ▃▃▃▃▃ ▂▂▂▂▂ ▆▆▆▆▆ ▄▄▄▄▄ █████ ▅▅▅▅▅▅ │ "
|
||||
" │ │ │ ▃▃▃▃▃ █████ █████ █████ █████ █████ ██████ │ "
|
||||
" │ │ │ 0 ───────────────────────────────────────────────────────── │ "
|
||||
" │ │ │ ▲ │ "
|
||||
" │ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 86 usage units │ │ Sep 2 · 103 messages │ "
|
||||
" │ ● Tasks 64.0% │ │ ● GPT-5.6-Sol 61 │ "
|
||||
" │ ● Code review 14.0% │ │ ● GPT-5.6-Terra 25 │ "
|
||||
" │ │ │ Sep 2 · 86 usage units │ "
|
||||
" │ │ │ ● Tasks 64.0% │ "
|
||||
" │ │ │ ● Code review 14.0% │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Plugins called ───────────────────────────────────────────╮ ╭─ 4 Skills used ──────────────────────────────────────────────╮ "
|
||||
" ╭─ 3 Messages ─────────────────────────────────────────────────╮ ╭─ 4 Plugins called ───────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ 186 calls │ │ 54 uses │ "
|
||||
" │ 29 │ │ 11 │ "
|
||||
" │ 40 ▅▅▅▅▅ ▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁ ▃▃▃▃▃▃ │ │ 20 │ "
|
||||
" │ █████ █████ █████ ██████ █████ █████ ██████ │ │ ▆▆▆▆▆ ▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄ ██████ │ "
|
||||
" │ 0 ────────────────────────────────────────────────────────── │ │ 0 ────────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ By model │ │ 186 calls │ "
|
||||
" │ 574 reported messages │ │ 29 │ "
|
||||
" │ 103 │ │ 40 ▅▅▅▅▅ ▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁ ▃▃▃▃▃▃ │ "
|
||||
" │ 150 ▃▃▃▃▃ ▁▁▁▁▁ ▂▂▂▂▂▂ │ │ █████ █████ █████ ██████ █████ █████ ██████ │ "
|
||||
" │ ▆▆▆▆▆ █████ █████ █████ ▆▆▆▆▆ █████ ██████ │ │ 0 ────────────────────────────────────────────────────────── │ "
|
||||
" │ 0 ───────────────────────────────────────────────────────── │ │ ▲ │ "
|
||||
" │ ▲ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ Sep 2 · 29 calls │ "
|
||||
" │ Sep 2 · 103 messages │ │ ● Slack 10 │ "
|
||||
" │ ● GPT-5.6-Sol 61 │ │ ● Figma 10 │ "
|
||||
" │ ● GPT-5.6-Terra 25 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 29 calls │ │ Sep 2 · 11 uses │ "
|
||||
" │ ● Slack 10 │ │ ● Testing 4 │ "
|
||||
" │ ● Figma 10 │ │ ● Code review 3 │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Skills used ──────────────────────────────────────────────╮ ╭─ ▸ 6 Top chats ────────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ No history has been reported. │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ 11 │ │ │ "
|
||||
" │ 20 │ │ │ "
|
||||
" │ ▆▆▆▆▆ ▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄ ██████ │ │ │ "
|
||||
" │ 0 ────────────────────────────────────────────────────────── │ │ │ "
|
||||
" │ ▲ │ │ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 11 uses │ │ │ "
|
||||
" │ ● Testing 4 │ │ │ "
|
||||
" │ ● Code review 3 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ ▸ 5 Top chats ────────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ No history has been reported. │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" tab/1–5 section · s sort · enter/z maximize "
|
||||
" tab/1–6 section · s sort · enter/z maximize "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Credits usage history] 2 Token usage history 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Credits usage history] 3 Token usage history 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Credits usage history By product "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 431.00 credits "
|
||||
" "
|
||||
" 150 "
|
||||
" "
|
||||
" ████████ "
|
||||
" 75 ▇▇▇▇▇▇▇▇ ▄▄▄▄▄▄▄▄ ████████ 64.00 "
|
||||
" ▅▅▅▅▅▅▅▅ ████████ ████████ ████████ ▇▇▇▇▇▇▇▇ "
|
||||
" ▇▇▇▇▇▇▇▇ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ▂▂▂▂▂▂▂▂ "
|
||||
" ▃▃▃▃▃▃▃▃ ████████ 64.00 "
|
||||
" 75 ▂▂▂▂▂▂▂▂ ████████ ████████ ████████ ▄▄▄▄▄▄▄▄ "
|
||||
" ▅▅▅▅▅▅▅▅ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ─────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
@@ -251,28 +312,28 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● IDE extension 10.00 "
|
||||
" ● Web 9.00 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 4.4M tokens "
|
||||
" "
|
||||
" 1,000,000 731,000 "
|
||||
" ▅▅▅▅▅▅▅▅ ▁▁▁▁▁▁▁▁ ████████ ▃▃▃▃▃▃▃▃ "
|
||||
" ▅▅▅▅▅▅▅▅ ▂▂▂▂▂▂▂▂ ████████ ████████ ████████ ████████ "
|
||||
" 500,000 ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 1M ▂▂▂▂▂▂▂▂ 731K "
|
||||
" ▆▆▆▆▆▆▆▆ ▃▃▃▃▃▃▃▃ ████████ ▅▅▅▅▅▅▅▅ "
|
||||
" 500K ████████ ▅▅▅▅▅▅▅▅ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ▆▆▆▆▆▆▆▆ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
@@ -282,23 +343,23 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● Output 43,000 "
|
||||
" Updated Sep 2 16:00 UTC "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history [4 Plugins called] 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 186 calls "
|
||||
" "
|
||||
" 40 29 "
|
||||
" ▇▇▇▇▇▇▇▇ "
|
||||
" ████████ ████████ ▄▄▄▄▄▄▄▄ █████████ "
|
||||
" 20 ████████ ████████ ████████ ████████ ▆▆▆▆▆▆▆▆ ████████ █████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ █████████ "
|
||||
" ████████ ▂▂▂▂▂▂▂▂ ▂▂▂▂▂▂▂▂▂ "
|
||||
" ████████ ▃▃▃▃▃▃▃▃ ████████ ▇▇▇▇▇▇▇▇ ▂▂▂▂▂▂▂▂ ▃▃▃▃▃▃▃▃ █████████ "
|
||||
" 20 ████████ ████████ ████████ ████████ ████████ ████████ █████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ █████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ █████████ "
|
||||
" 0 ──────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -313,24 +374,24 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called [5 Skills used] 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 54 uses "
|
||||
" "
|
||||
" 20 "
|
||||
" "
|
||||
" 11 "
|
||||
" 10 ▁▁▁▁▁▁▁▁ ▆▆▆▆▆▆▆▆▆ "
|
||||
" ▆▆▆▆▆▆▆▆ ████████ ▆▆▆▆▆▆▆▆ ▃▃▃▃▃▃▃▃ █████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ▆▆▆▆▆▆▆▆ █████████ "
|
||||
" ▂▂▂▂▂▂▂▂▂ "
|
||||
" 10 ▃▃▃▃▃▃▃▃ ▅▅▅▅▅▅▅▅ ▃▃▃▃▃▃▃▃ █████████ "
|
||||
" ████████ ▆▆▆▆▆▆▆▆ ████████ ████████ ████████ ▄▄▄▄▄▄▄▄ █████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ █████████ "
|
||||
" 0 ──────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
@@ -344,12 +405,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 "
|
||||
" Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -374,71 +436,70 @@ expression: "screens.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" ╭─ 1 Credits usage history ────────────────────────────────────╮ ╭─ 2 Token usage history ──────────────────────────────────────╮ "
|
||||
" ╭─ 1 Summary ──────────────────────────────────────────────────╮ ╭─ 2 Credits usage history ────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d · All models │ "
|
||||
" │ By product │ │ By token type │ "
|
||||
" │ 431.00 credits │ │ 4,377,500 tokens │ "
|
||||
" │ │ │ 731,000 │ "
|
||||
" │ 150 ▁▁▁▁▁ ▃▃▃▃▃ 64.00 │ │ 1,000,000 ▁▁▁▁ ▄▄▄▄▄ ▃▃▃▃ ▅▅▅▅ ▃▃▃▃▃ │ "
|
||||
" │ 0 ▄▄▄▄▄ ▆▆▆▆▆ █████ █████ █████ ▆▆▆▆▆▆ │ │ ▂▂▂▂ ████ ████ █████ ████ ████ █████ │ "
|
||||
" │ 0 ───────────────────────────────────────────────────────── │ │ 0 ─────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ No history has been reported. │ │ 7d │ "
|
||||
" │ │ │ By product │ "
|
||||
" │ │ │ 431.00 credits │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ 150 ▁▁▁▁▁ ▃▃▃▃▃ 64.00 │ "
|
||||
" │ │ │ 0 ▄▄▄▄▄ ▆▆▆▆▆ █████ █████ █████ ▆▆▆▆▆▆ │ "
|
||||
" │ │ │ 0 ───────────────────────────────────────────────────────── │ "
|
||||
" │ │ │ ▲ │ "
|
||||
" │ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ Sep 2 · 64.00 credits │ "
|
||||
" │ │ │ ● CLI 23.00 │ "
|
||||
" │ │ │ ● Desktop app 22.00 │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 64.00 credits │ │ Sep 2 · 731,000 tokens │ "
|
||||
" │ ● CLI 23.00 │ │ ● Cached input 602,000 │ "
|
||||
" │ ● Desktop app 22.00 │ │ ● Uncached input 86,000 │ "
|
||||
" │ │ │ Updated Sep 2 16:00 UTC │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Plugins called ───────────────────────────────────────────╮ ╭─ 4 Skills used ──────────────────────────────────────────────╮ "
|
||||
" ╭─ 3 Token usage history ──────────────────────────────────────╮ ╭─ 4 Plugins called ───────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ 186 calls │ │ 54 uses │ "
|
||||
" │ 29 │ │ 11 │ "
|
||||
" │ 40 ▅▅▅▅▅ ▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁ ▃▃▃▃▃▃ │ │ 20 │ "
|
||||
" │ █████ █████ █████ ██████ █████ █████ ██████ │ │ ▆▆▆▆▆ ▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄ ██████ │ "
|
||||
" │ 0 ────────────────────────────────────────────────────────── │ │ 0 ────────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ 7d · All models │ │ 7d │ "
|
||||
" │ By token type │ │ 186 calls │ "
|
||||
" │ 4.4M tokens │ │ 29 │ "
|
||||
" │ 731K │ │ 40 ▅▅▅▅▅ ▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁ ▃▃▃▃▃▃ │ "
|
||||
" │ 1M ▁▁▁▁▁ ▄▄▄▄▄ ▃▃▃▃▃ ▅▅▅▅▅ ▃▃▃▃▃ │ │ █████ █████ █████ ██████ █████ █████ ██████ │ "
|
||||
" │ ▂▂▂▂▂ █████ █████ █████ █████ █████ █████ │ │ 0 ────────────────────────────────────────────────────────── │ "
|
||||
" │ 0 ──────────────────────────────────────────────────────── │ │ ▲ │ "
|
||||
" │ ▲ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ Sep 2 · 29 calls │ "
|
||||
" │ Sep 2 · 731,000 tokens │ │ ● Slack 10 │ "
|
||||
" │ ● Cached input 602,000 │ │ ● Figma 10 │ "
|
||||
" │ ● Uncached input 86,000 │ │ │ "
|
||||
" │ Updated Sep 2 16:00 UTC │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 29 calls │ │ Sep 2 · 11 uses │ "
|
||||
" │ ● Slack 10 │ │ ● Testing 4 │ "
|
||||
" │ ● Figma 10 │ │ ● Code review 3 │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Skills used ──────────────────────────────────────────────╮ ╭─ ▸ 6 Top chats ────────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 30d active · lifetime credits │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ 11 │ │ Q3 planning analysis 120.00 │ "
|
||||
" │ 20 │ │ Refactor billing usage 113.00 │ "
|
||||
" │ ▆▆▆▆▆ ▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄ ██████ │ │ Other usage 109.00 │ "
|
||||
" │ 0 ────────────────────────────────────────────────────────── │ │ Launch readiness review 85.00 │ "
|
||||
" │ ▲ │ │ Plan the week 0.00 │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ Local chats · excludes subagents │ "
|
||||
" │ Sep 2 · 11 uses │ │ │ "
|
||||
" │ ● Testing 4 │ │ │ "
|
||||
" │ ● Code review 3 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ ▸ 5 Top chats ────────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ 30d active · lifetime credits │ "
|
||||
" │ │ "
|
||||
" │ Q3 planning analysis 120.00 │ "
|
||||
" │ Refactor billing usage 113.00 │ "
|
||||
" │ Other usage 109.00 │ "
|
||||
" │ Launch readiness review 85.00 │ "
|
||||
" │ Plan the week 0.00 │ "
|
||||
" │ │ "
|
||||
" │ Local chats · excludes subagents │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" ╰────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" tab/1–5 section · enter/z maximize "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+7
-7
@@ -4,7 +4,8 @@ expression: "format!(\"{dollars}\\n{narrow}\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 "
|
||||
" Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -29,15 +30,14 @@ expression: "format!(\"{dollars}\\n{narrow}\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" Credits usage history Token usage "
|
||||
" history Plugins called Skills used "
|
||||
" [Top chats] "
|
||||
" Summary Credits usage history Token "
|
||||
" usage history Plugins called Skills "
|
||||
" used [Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────── "
|
||||
@@ -59,7 +59,7 @@ expression: "format!(\"{dollars}\\n{narrow}\")"
|
||||
" and subagent usage "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ "
|
||||
" row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc "
|
||||
" back · q close "
|
||||
|
||||
+24
-24
@@ -2,25 +2,25 @@
|
||||
source: tui/src/analytics/account_layout_tests.rs
|
||||
expression: "format!(\"{all}\\n{filtered}\")"
|
||||
---
|
||||
" Analytics · Live account · analytics@example.test · account-a "
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 4.4M tokens "
|
||||
" "
|
||||
" 1,000,000 731,000 "
|
||||
" ▅▅▅▅▅▅▅▅ ▁▁▁▁▁▁▁▁ ████████ ▃▃▃▃▃▃▃▃ "
|
||||
" ▅▅▅▅▅▅▅▅ ▂▂▂▂▂▂▂▂ ████████ ████████ ████████ ████████ "
|
||||
" 500,000 ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 1M ▂▂▂▂▂▂▂▂ 731K "
|
||||
" ▆▆▆▆▆▆▆▆ ▃▃▃▃▃▃▃▃ ████████ ▅▅▅▅▅▅▅▅ "
|
||||
" 500K ████████ ▅▅▅▅▅▅▅▅ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ▆▆▆▆▆▆▆▆ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
@@ -30,28 +30,28 @@ expression: "format!(\"{all}\\n{filtered}\")"
|
||||
" ● Output 43,000 "
|
||||
" Updated Sep 2 16:00 UTC "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account · analytics@example.test · account-a "
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" GPT-5.5 · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 4.4M tokens "
|
||||
" "
|
||||
" 1,000,000 731,000 "
|
||||
" ▅▅▅▅▅▅▅▅ ▁▁▁▁▁▁▁▁ ████████ ▃▃▃▃▃▃▃▃ "
|
||||
" ▅▅▅▅▅▅▅▅ ▂▂▂▂▂▂▂▂ ████████ ████████ ████████ ████████ "
|
||||
" 500,000 ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 1M ▂▂▂▂▂▂▂▂ 731K "
|
||||
" ▆▆▆▆▆▆▆▆ ▃▃▃▃▃▃▃▃ ████████ ▅▅▅▅▅▅▅▅ "
|
||||
" 500K ████████ ▅▅▅▅▅▅▅▅ ████████ ████████ ████████ ████████ "
|
||||
" ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" ▆▆▆▆▆▆▆▆ ████████ ████████ ████████ ████████ ████████ ████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
@@ -61,5 +61,5 @@ expression: "format!(\"{all}\\n{filtered}\")"
|
||||
" ● Output 43,000 "
|
||||
" Updated Sep 2 16:00 UTC "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: tui/src/analytics/account_layout_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
" Analytics · Live account · analytics@example.test "
|
||||
" "
|
||||
" [1 Summary] "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Account "
|
||||
" "
|
||||
" 12.3K — — — — "
|
||||
" Lifetime tokens Peak tokens Longest chat Current streak Longest streak "
|
||||
" "
|
||||
" Token activity [Daily] Weekly Cumulative "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Token activity history unavailable "
|
||||
" "
|
||||
" Activity insights Most used plugins and skills "
|
||||
" "
|
||||
" Fast Mode — Plugin and skill usage unavailable. "
|
||||
" Most used reasoning — "
|
||||
" Skills explored — "
|
||||
" Total skills used — "
|
||||
" Total chats — "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–1 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+16
-17
@@ -1,29 +1,28 @@
|
||||
---
|
||||
source: tui/src/analytics/account_layout_tests.rs
|
||||
assertion_line: 341
|
||||
expression: "screen(&mut view, 110, 24)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used "
|
||||
" 1 Summary [2 Credits usage history] 3 Token usage history 4 Plugins called 5 Skills used "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ▎ Credits usage history By product "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 731,000 "
|
||||
" 1,000,000 ▁▁▁▁▁▁▁▁▁ ▄▄▄▄▄▄▄▄▄ ▃▃▃▃▃▃▃▃ ▅▅▅▅▅▅▅▅▅ ▃▃▃▃▃▃▃▃▃ "
|
||||
" ▂▂▂▂▂▂▂▂ █████████ ████████ █████████ ████████ █████████ █████████ "
|
||||
" 0 ─────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 431.00 credits "
|
||||
" "
|
||||
" 150 ▁▁▁▁▁▁▁▁▁ ▃▃▃▃▃▃▃▃▃ 64.00 "
|
||||
" 0 ▄▄▄▄▄▄▄▄▄ ▆▆▆▆▆▆▆▆▆▆ █████████ ██████████ █████████ ▆▆▆▆▆▆▆▆▆▆ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
" Token type Tokens "
|
||||
" ● Cached input 602,000 "
|
||||
" ● Uncached input 86,000 "
|
||||
" ● Output 43,000 "
|
||||
" tab/1–4 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
" Sep 2 · 64.00 credits "
|
||||
" Product Credits "
|
||||
" ● CLI 23.00 "
|
||||
" ● Desktop app 22.00 "
|
||||
" ● IDE extension 10.00 "
|
||||
" ● Web 9.00 "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+7
-7
@@ -4,7 +4,7 @@ expression: "screens.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -32,13 +32,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" Credits usage history Token usage history Plugins "
|
||||
" called Skills used [Top chats] "
|
||||
" Summary Credits usage history Token usage history "
|
||||
" Plugins called Skills used [Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────── "
|
||||
@@ -64,13 +64,13 @@ expression: "screens.join(\"\\n\")"
|
||||
" Local chats · excludes archived chats and subagent "
|
||||
" usage "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter "
|
||||
" details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -98,5 +98,5 @@ expression: "screens.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+2
-3
@@ -1,11 +1,10 @@
|
||||
---
|
||||
source: tui/src/analytics/chat_panel_tests.rs
|
||||
assertion_line: 123
|
||||
expression: output
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -31,5 +30,5 @@ expression: output
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ expression: "screens.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -24,12 +24,12 @@ expression: "screens.join(\"\\n\")"
|
||||
" Showing 1–9 of 30 chats "
|
||||
" Local chats · excludes archived chats and subagent usage "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -49,12 +49,12 @@ expression: "screens.join(\"\\n\")"
|
||||
" Showing 22–30 of 30 chats "
|
||||
" Local chats · excludes archived chats and subagent usage "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -74,5 +74,5 @@ expression: "screens.join(\"\\n\")"
|
||||
" Showing 26–30 of 30 chats "
|
||||
" Local chats · excludes archived chats and subagent usage "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+2
-3
@@ -1,11 +1,10 @@
|
||||
---
|
||||
source: tui/src/analytics/chat_panel_tests.rs
|
||||
assertion_line: 205
|
||||
expression: "screen(&mut view, 110, 24)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -25,5 +24,5 @@ expression: "screen(&mut view, 110, 24)"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+24
-25
@@ -1,11 +1,10 @@
|
||||
---
|
||||
source: tui/src/analytics/chat_panel_tests.rs
|
||||
assertion_line: 165
|
||||
expression: "screens.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -29,34 +28,34 @@ expression: "screens.join(\"\\n\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 29 calls │ │ Sep 2 · 11 uses │ "
|
||||
" │ ● Slack 10 │ │ ● Testing 4 │ "
|
||||
" │ ● Figma 10 │ │ ● Code review 3 │ "
|
||||
" │ │ │ │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ │ "
|
||||
" │ │ │ Sep 2 · 29 calls │ "
|
||||
" │ Sep 2 · 731,000 tokens │ │ ● Slack 10 │ "
|
||||
" │ ● Cached input 602,000 │ │ ● Figma 10 │ "
|
||||
" │ … z to maximize │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰─────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ ▸ 5 Top chats ─────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ 30d active · lifetime credits │ "
|
||||
" │ │ "
|
||||
" │ Chat usage unavailable — │ "
|
||||
" │ │ "
|
||||
" │ Local chats · excludes subagents │ "
|
||||
" │ Some estimates unavailable │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" ╰─────────────────────────────────────────────────╯ "
|
||||
" tab/1–5 section · enter/z maximize "
|
||||
" ╭─ 5 Skills used ───────────────────────────────╮ ╭─ ▸ 6 Top chats ─────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 30d active · lifetime credits │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ 11 │ │ Chat usage unavailable — │ "
|
||||
" │ 20 ▃▃▃▃ ▂▂▂▂ ▃▃▃▃ ▃▃▃▃ ▂▂▂▂ ▂▂▂▂ ▄▄▄▄ │ │ │ "
|
||||
" │ 0 ─────────────────────────────────────────── │ │ Local chats · excludes subagents │ "
|
||||
" │ ▲ │ │ Some estimates unavailable │ "
|
||||
" │ 27 28 29 30 31 1 2 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 11 uses │ │ │ "
|
||||
" │ ● Testing 4 │ │ │ "
|
||||
" │ ● Code review 3 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰─────────────────────────────────────────────────╯ ╰─────────────────────────────────────────────────╯ "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
---
|
||||
source: tui/src/analytics/compact_tests.rs
|
||||
expression: "screenshots.join(\"\\n\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 12.3B tokens "
|
||||
" "
|
||||
" 15B 12.3B "
|
||||
" ▄▄▄▄▄▄▄▄ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" 7.5B ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" — — — — — — ████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 12,280,365,226 tokens "
|
||||
" Token type Tokens "
|
||||
" ● Example 12,280,365,226 "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history [4 Plugins called] 5 Skills used 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 9.7K calls "
|
||||
" 9.7K "
|
||||
" 10K ▆▆▆▆▆▆▆▆ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" 5K ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" — — — — — — ████████ "
|
||||
" 0 ─────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 9,749 calls "
|
||||
" Plugin Calls "
|
||||
" ● Example 9,749 "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called [5 Skills used] 6 "
|
||||
" Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 2.9K uses "
|
||||
" 2.9K "
|
||||
" 3K ▅▅▅▅▅▅▅▅ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" 1.5K ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" ████████ "
|
||||
" — — — — — — ████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 2,901 uses "
|
||||
" Skill Uses "
|
||||
" ● Example 2,901 "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+2
-3
@@ -1,11 +1,10 @@
|
||||
---
|
||||
source: tui/src/analytics/consumer_refresh_tests.rs
|
||||
assertion_line: 61
|
||||
expression: "screen(&mut view, 110, 30)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -31,5 +30,5 @@ expression: "screen(&mut view, 110, 30)"
|
||||
" Showing 1–3 of 3 chats "
|
||||
" Local chats · includes discovered descendants · excludes archived roots "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
---
|
||||
source: tui/src/analytics/consumer_refresh_tests.rs
|
||||
assertion_line: 66
|
||||
expression: "screen(&mut view, 58, 30)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" Total usage history Messages Plugins called Skills "
|
||||
" used [Top chats] "
|
||||
" Summary Total usage history Messages Plugins called "
|
||||
" Skills used [Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────── "
|
||||
@@ -30,6 +29,6 @@ expression: "screen(&mut view, 58, 30)"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · "
|
||||
" enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+3
-4
@@ -1,11 +1,11 @@
|
||||
---
|
||||
source: tui/src/analytics/consumer_refresh_tests.rs
|
||||
assertion_line: 43
|
||||
expression: "screen(&mut view, 90, 30)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called 5 Skills used [6 Top "
|
||||
" chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -30,6 +30,5 @@ expression: "screen(&mut view, 90, 30)"
|
||||
" Local chats · includes discovered descendants · excludes archived roots "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+66
-67
@@ -1,77 +1,93 @@
|
||||
---
|
||||
source: tui/src/analytics/dashboard_tests.rs
|
||||
assertion_line: 83
|
||||
expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" ╭─ ▸ 1 Total usage history ────────────────────────────────────────╮ ╭─ 2 Messages ───────────────────────────────────────────────────╮ "
|
||||
" ╭─ 1 Summary ────────────────────────────────────────────────────╮ ╭─ ▸ 2 Total usage history ────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ By feature │ │ By model │ "
|
||||
" │ Total usage · relative units │ │ 574 reported messages │ "
|
||||
" │ 86 │ │ 103 │ "
|
||||
" │ 100 ▃▃▃▃▃ ▂▂▂▂▂▂ ▆▆▆▆▆ ▄▄▄▄▄▄ █████ ▅▅▅▅▅▅ │ │ 150 ▃▃▃▃▃▃ ▁▁▁▁▁ ▂▂▂▂▂▂ │ "
|
||||
" │ ▃▃▃▃▃ █████ ██████ █████ ██████ █████ ██████ │ │ ▆▆▆▆▆ █████ ██████ █████ ▆▆▆▆▆▆ █████ ██████ │ "
|
||||
" │ 0 ─────────────────────────────────────────────────────────── │ │ 0 ─────────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ No history has been reported. │ │ 7d │ "
|
||||
" │ │ │ By feature │ "
|
||||
" │ │ │ Total usage · relative units │ "
|
||||
" │ │ │ 86 │ "
|
||||
" │ │ │ 100 ▃▃▃▃▃ ▂▂▂▂▂▂ ▆▆▆▆▆ ▄▄▄▄▄▄ █████ ▅▅▅▅▅▅ │ "
|
||||
" │ │ │ ▃▃▃▃▃ █████ ██████ █████ ██████ █████ ██████ │ "
|
||||
" │ │ │ 0 ─────────────────────────────────────────────────────────── │ "
|
||||
" │ │ │ ▲ │ "
|
||||
" │ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 86 usage units │ │ Sep 2 · 103 messages │ "
|
||||
" │ ● Tasks 64.0% │ │ ● GPT-5.6-Sol 61 │ "
|
||||
" │ ● Code review 14.0% │ │ ● GPT-5.6-Terra 25 │ "
|
||||
" │ │ │ Sep 2 · 86 usage units │ "
|
||||
" │ │ │ ● Tasks 64.0% │ "
|
||||
" │ │ │ ● Code review 14.0% │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Plugins called ─────────────────────────────────────────────╮ ╭─ 4 Skills used ────────────────────────────────────────────────╮ "
|
||||
" ╭─ 3 Messages ───────────────────────────────────────────────────╮ ╭─ 4 Plugins called ─────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ 186 calls │ │ 54 uses │ "
|
||||
" │ 29 │ │ 11 │ "
|
||||
" │ 40 ▅▅▅▅▅ ▁▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁▁ ▃▃▃▃▃▃ │ │ 20 │ "
|
||||
" │ █████ ██████ █████ ██████ █████ ██████ ██████ │ │ ▆▆▆▆▆ ▄▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄▄ ██████ │ "
|
||||
" │ 0 ──────────────────────────────────────────────────────────── │ │ 0 ──────────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 29 calls │ │ Sep 2 · 11 uses │ "
|
||||
" │ ● Slack 10 │ │ ● Testing 4 │ "
|
||||
" │ ● Figma 10 │ │ ● Code review 3 │ "
|
||||
" │ │ │ │ "
|
||||
" │ By model │ │ 186 calls │ "
|
||||
" │ 574 reported messages │ │ 29 │ "
|
||||
" │ 103 │ │ 40 ▅▅▅▅▅ ▁▁▁▁▁▁ ▃▃▃▃▃ ▂▂▂▂▂▂ ▁▁▁▁▁▁ ▃▃▃▃▃▃ │ "
|
||||
" │ 150 ▃▃▃▃▃▃ ▁▁▁▁▁ ▂▂▂▂▂▂ │ │ █████ ██████ █████ ██████ █████ ██████ ██████ │ "
|
||||
" │ ▆▆▆▆▆ █████ ██████ █████ ▆▆▆▆▆▆ █████ ██████ │ │ 0 ──────────────────────────────────────────────────────────── │ "
|
||||
" │ 0 ─────────────────────────────────────────────────────────── │ │ ▲ │ "
|
||||
" │ ▲ │ │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ Sep 2 · 29 calls │ "
|
||||
" │ Sep 2 · 103 messages │ │ ● Slack 10 │ "
|
||||
" │ ● GPT-5.6-Sol 61 │ │ ● Figma 10 │ "
|
||||
" │ ● GPT-5.6-Terra 25 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Top chats ──────────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ No history has been reported. │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" tab/1–5 section · enter/z maximize "
|
||||
" ╭─ 5 Skills used ────────────────────────────────────────────────╮ ╭─ 6 Top chats ──────────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ No history has been reported. │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ 11 │ │ │ "
|
||||
" │ 20 │ │ │ "
|
||||
" │ ▆▆▆▆▆ ▄▄▄▄▄▄ ▇▇▇▇▇ ▆▆▆▆▆▆ ▅▅▅▅▅ ▄▄▄▄▄▄ ██████ │ │ │ "
|
||||
" │ 0 ──────────────────────────────────────────────────────────── │ │ │ "
|
||||
" │ ▲ │ │ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Sep 2 · 11 uses │ │ │ "
|
||||
" │ ● Testing 4 │ │ │ "
|
||||
" │ ● Code review 3 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" ╭─ ▸ 1 Total usage history ────────────────────────────────────────╮ "
|
||||
" ╭─ 1 Summary ────────────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ No history has been reported. │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ ▸ 2 Total usage history ────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ 7d │ "
|
||||
" │ By feature │ "
|
||||
@@ -88,7 +104,7 @@ expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
" │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 2 Messages ───────────────────────────────────────────────────╮ "
|
||||
" ╭─ 3 Messages ───────────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ 7d │ "
|
||||
" │ By model │ "
|
||||
@@ -96,23 +112,6 @@ expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
" │ 103 │ "
|
||||
" │ 150 ▃▃▃▃▃ ▄▄▄▄▄ ▅▅▅▅▅▅ ▄▄▄▄▄ ▃▃▃▃▃▃ ▄▄▄▄▄ ▅▅▅▅▅▅ │ "
|
||||
" │ 0 ─────────────────────────────────────────────────────────── │ "
|
||||
" │ ▲ │ "
|
||||
" │ Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 │ "
|
||||
" │ │ "
|
||||
" │ Sep 2 · 103 messages │ "
|
||||
" │ ● GPT-5.6-Sol 61 │ "
|
||||
" │ ● GPT-5.6-Terra 25 │ "
|
||||
" │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Plugins called ─────────────────────────────────────────────╮ "
|
||||
" │ │ "
|
||||
" │ 7d │ "
|
||||
" │ Access denied for this report. │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" │ │ "
|
||||
" tab/1–5 section · enter/z maximize "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q "
|
||||
" close "
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: "screen(&mut view, 64, 60)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [Summary] Total usage history Messages Plugins called "
|
||||
" Skills used Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Example User "
|
||||
" @example.user "
|
||||
" Plus "
|
||||
" "
|
||||
" 142.3B 7B 44h 7m "
|
||||
" Lifetime tokens Peak tokens Longest chat "
|
||||
" "
|
||||
" 158 days 160 days "
|
||||
" Current streak Longest streak "
|
||||
" "
|
||||
" Token activity Daily Weekly [Cumulative] "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Mar Apr May Jun Jul Aug "
|
||||
" max █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" 0 █ "
|
||||
" "
|
||||
" Running total · top 300 "
|
||||
" "
|
||||
" Activity insights "
|
||||
" "
|
||||
" Fast Mode 37% "
|
||||
" Most used reasoning high · 26% "
|
||||
" Skills explored 147 "
|
||||
" Total skills used 13,190 "
|
||||
" Total chats 21,482 "
|
||||
" "
|
||||
" Most used plugins and skills "
|
||||
" "
|
||||
" $review 1,142 runs "
|
||||
" @GitHub 1,059 runs "
|
||||
" "
|
||||
" Statistics as of Sep 9, 2026 "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: "screen(&mut view, 140, 44)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Example User "
|
||||
" @example.user "
|
||||
" Plus "
|
||||
" "
|
||||
" 142.3B 7B 44h 7m 158 days 160 days "
|
||||
" Lifetime tokens Peak tokens Longest chat Current streak Longest streak "
|
||||
" "
|
||||
" Token activity [Daily] Weekly Cumulative "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Oct Nov Dec Jan Feb Mar Apr May Jun Jul Aug "
|
||||
" Su □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Mo □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Tu □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ "
|
||||
" We □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ "
|
||||
" Th □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Fr □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" Sa □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ "
|
||||
" "
|
||||
" Less □ ■ ■ ■ ■ More "
|
||||
" "
|
||||
" Activity insights Most used plugins and skills "
|
||||
" "
|
||||
" Fast Mode 37% $review 1,142 runs "
|
||||
" Most used reasoning high · 26% @GitHub 1,059 runs "
|
||||
" Skills explored 147 "
|
||||
" Total skills used 13,190 "
|
||||
" Total chats 21,482 "
|
||||
" "
|
||||
" Statistics as of Sep 9, 2026 "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: "screen(&mut view, 144, 64)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" ╭─ ▸ 1 Summary ────────────────────────────────────────────────────╮ ╭─ 2 Total usage history ────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ Example User │ │ 7d │ "
|
||||
" │ @example.user │ │ By feature │ "
|
||||
" │ Plus │ │ Total usage · relative units │ "
|
||||
" │ │ │ │ "
|
||||
" │ Lifetime tokens 142.3B │ │ 1 │ "
|
||||
" │ Peak tokens 7B │ │ — — — — — — — │ "
|
||||
" │ Longest chat 44h 7m │ │ 0 ─────────────────────────────────────────────────────────── │ "
|
||||
" │ Current streak 158 days │ │ ▲ │ "
|
||||
" │ Longest streak 160 days │ │ Sep 3 Sep 4 Sep 5 Sep 6 Sep 7 Sep 8 Sep 9 │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ Sep 9 · Not reported │ "
|
||||
" │ │ │ ● Tasks — │ "
|
||||
" │ │ │ ● Code review — │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 3 Messages ───────────────────────────────────────────────────╮ ╭─ 4 Plugins called ─────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ 7d │ "
|
||||
" │ By model │ │ 186 calls │ "
|
||||
" │ 574 reported messages │ │ │ "
|
||||
" │ │ │ 2 │ "
|
||||
" │ 2 │ │ — — — — — — — │ "
|
||||
" │ — — — — — — — │ │ 0 ───────────────────────────────────────────────────────────── │ "
|
||||
" │ 0 ───────────────────────────────────────────────────────────── │ │ ▲ │ "
|
||||
" │ ▲ │ │ Sep 3 Sep 4 Sep 5 Sep 6 Sep 7 Sep 8 Sep 9 │ "
|
||||
" │ Sep 3 Sep 4 Sep 5 Sep 6 Sep 7 Sep 8 Sep 9 │ │ │ "
|
||||
" │ │ │ Data not reported for Sep 9. │ "
|
||||
" │ Data not reported for Sep 9. │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" ╭─ 5 Skills used ────────────────────────────────────────────────╮ ╭─ 6 Top chats ──────────────────────────────────────────────────╮ "
|
||||
" │ │ │ │ "
|
||||
" │ 7d │ │ No history has been reported. │ "
|
||||
" │ 54 uses │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ 2 │ │ │ "
|
||||
" │ — — — — — — — │ │ │ "
|
||||
" │ 0 ───────────────────────────────────────────────────────────── │ │ │ "
|
||||
" │ ▲ │ │ │ "
|
||||
" │ Sep 3 Sep 4 Sep 5 Sep 6 Sep 7 Sep 8 Sep 9 │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ Data not reported for Sep 9. │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" │ │ │ │ "
|
||||
" ╰──────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────╯ "
|
||||
" "
|
||||
" tab/1–6 section · enter/z maximize "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: rendered
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Business "
|
||||
" "
|
||||
" 0 — — — — "
|
||||
" Lifetime tokens Peak tokens Longest chat Current streak Longest streak "
|
||||
" "
|
||||
" Token activity [Daily] Weekly Cumulative "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Token activity history unavailable "
|
||||
" "
|
||||
" Activity insights Most used plugins and skills "
|
||||
" "
|
||||
" Fast Mode 0% No reported plugins or skills. "
|
||||
" Most used reasoning — "
|
||||
" Skills explored — "
|
||||
" Total skills used — "
|
||||
" Total chats 0 "
|
||||
" "
|
||||
" Some profile statistics are unavailable. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: "screen(&mut view, 64, 18)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [Summary] Total usage history Messages Plugins called "
|
||||
" Skills used Top chats "
|
||||
" "
|
||||
" Most used reasoning high · 26% "
|
||||
" Skills explored 147 "
|
||||
" Total skills used 13,190 "
|
||||
" Total chats 21,482 "
|
||||
" "
|
||||
" Most used plugins and skills "
|
||||
" "
|
||||
" $review 1,142 runs "
|
||||
" @GitHub 1,059 runs "
|
||||
" "
|
||||
" Statistics as of Sep 9, 2026 "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
source: tui/src/analytics/summary_tests.rs
|
||||
expression: "screen(&mut view, 110, 44)"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" Example User "
|
||||
" @example.user "
|
||||
" Plus "
|
||||
" "
|
||||
" 142.3B 7B 44h 7m 158 days 160 days "
|
||||
" Lifetime tokens Peak tokens Longest chat Current streak Longest streak "
|
||||
" "
|
||||
" Token activity Daily [Weekly] Cumulative "
|
||||
" Last 12 months "
|
||||
" "
|
||||
" Oct Nov Dec Jan Feb Mar Apr May Jun Jul Aug "
|
||||
" max █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" █ "
|
||||
" 0 █ "
|
||||
" "
|
||||
" Each column = 1 week · tallest 300 "
|
||||
" "
|
||||
" Activity insights Most used plugins and skills "
|
||||
" "
|
||||
" Fast Mode 37% $review 1,142 runs "
|
||||
" Most used reasoning high · 26% @GitHub 1,059 runs "
|
||||
" Skills explored 147 "
|
||||
" Total skills used 13,190 "
|
||||
" Total chats 21,482 "
|
||||
" "
|
||||
" Statistics as of Sep 9, 2026 "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
+45
-45
@@ -7,34 +7,34 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 4.4M tokens "
|
||||
" "
|
||||
" 1,000,000 "
|
||||
" 1M "
|
||||
" "
|
||||
" ▂▂▂▂▂▂▂▂▂▂ "
|
||||
" ██████████ 731,000 "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ ▁▁▁▁▁▁▁▁▁▁ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ▆▆▆▆▆▆▆▆▆▆ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 500,000 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" ▂▂▂▂▂▂▂▂▂▂ "
|
||||
" ██████████ 731K "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ ▁▁▁▁▁▁▁▁▁▁ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ▆▆▆▆▆▆▆▆▆▆ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 500K ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● Output 43,000 "
|
||||
" Updated Sep 2 16:00 UTC "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,34 +61,34 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 27:3 52:2 96:0
|
||||
3: 0:0 2:2 38:3 63:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 23:0 105:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
7: 0:0 2:2 29:0
|
||||
8: 0:0 2:3 18:0
|
||||
8: 0:0 2:3 13:0
|
||||
9: 0:0
|
||||
10: 0:0 3:2 12:0
|
||||
10: 0:0 5:2 7:0
|
||||
11: 0:0
|
||||
12: 0:0 90:5 100:0
|
||||
13: 0:0 90:5 100:0 107:3 114:0
|
||||
14: 0:0 60:5 70:0 90:6 100:0 105:5 115:0
|
||||
15: 0:0 60:6 70:0 75:5 85:0 90:7 100:0 105:5 115:0
|
||||
16: 0:0 30:5 40:0 60:6 70:0 75:6 85:0 90:7 100:0 105:6 115:0
|
||||
17: 0:0 30:6 40:0 60:7 70:0 75:6 85:0 90:7 100:0 105:7 115:0
|
||||
18: 0:0 30:6 40:0 45:5 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
19: 0:0 5:2 12:0 30:7 40:0 45:6 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
20: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
21: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
22: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
23: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
24: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
25: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
26: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
27: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
28: 0:0 11:2 12:0 13:4 118:0
|
||||
29: 0:0 110:3 111:0
|
||||
30: 0:0 17:2 23:0 32:2 38:0 47:2 53:0 62:2 68:0 77:2 83:0 93:2 98:0 108:8 113:0
|
||||
12: 0:0 89:5 99:0
|
||||
13: 0:0 89:5 99:0 107:3 111:0
|
||||
14: 0:0 57:5 67:0 89:6 99:0 105:5 115:0
|
||||
15: 0:0 57:6 67:0 73:5 83:0 89:7 99:0 105:5 115:0
|
||||
16: 0:0 26:5 36:0 57:6 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
17: 0:0 26:6 36:0 57:7 67:0 73:6 83:0 89:7 99:0 105:7 115:0
|
||||
18: 0:0 26:6 36:0 42:5 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
19: 0:0 3:2 7:0 26:7 36:0 42:6 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
20: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
21: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
22: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
23: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
24: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
25: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 6:2 7:0 8:4 118:0
|
||||
29: 0:0 109:3 110:0
|
||||
30: 0:0 12:2 18:0 27:2 33:0 43:2 49:0 59:2 65:0 74:2 80:0 91:2 96:0 107:8 112:0
|
||||
31: 0:0
|
||||
32: 0:0 2:4 58:0
|
||||
33: 0:0 2:1 7:3 24:0
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 50:3 70:2 96:0
|
||||
3: 0:0 2:2 61:3 81:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 18:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+35
-35
@@ -7,34 +7,34 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Credits usage history] 2 Token usage history 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Credits usage history] 3 Token usage history 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Credits usage history By product "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 17,240.00 credits "
|
||||
" 17.2K credits "
|
||||
" "
|
||||
" 5,000 "
|
||||
" 5K "
|
||||
" "
|
||||
" ▄▄▄▄▄▄▄▄▄▄ "
|
||||
" ██████████ "
|
||||
" ██████████ "
|
||||
" ██████████ "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ "
|
||||
" ██████████ ██████████ ██████████ 2,560.00 "
|
||||
" 2,500 ██████████ ██████████ ██████████ ▅▅▅▅▅▅▅▅▅▅ "
|
||||
" ▅▅▅▅▅▅▅▅▅▅ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ "
|
||||
" ██████████ ██████████ ██████████ 2,560.00 "
|
||||
" 2.5K ██████████ ██████████ ██████████ ▅▅▅▅▅▅▅▅▅▅ "
|
||||
" ▅▅▅▅▅▅▅▅▅▅ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 2,560.00 credits "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● IDE extension 400.00 "
|
||||
" ● Web 360.00 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,13 +61,13 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:3 29:2 96:0
|
||||
3: 0:0 2:2 13:3 40:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 25:0 108:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
7: 0:0 2:3 19:0
|
||||
7: 0:0 2:3 15:0
|
||||
8: 0:0
|
||||
9: 0:0 3:2 8:0
|
||||
9: 0:0 5:2 7:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
@@ -75,20 +75,20 @@ Rows (column:palette):
|
||||
14: 0:0 89:5 99:0
|
||||
15: 0:0 89:5 99:0
|
||||
16: 0:0 89:6 99:0
|
||||
17: 0:0 58:5 68:0 89:6 99:0 105:3 113:0
|
||||
18: 0:0 3:2 8:0 58:5 68:0 73:5 83:0 89:6 99:0
|
||||
19: 0:0 58:6 68:0 73:5 83:0 89:6 99:0
|
||||
20: 0:0 58:6 68:0 73:6 83:0 89:6 99:0 105:5 115:0
|
||||
21: 0:0 42:5 52:0 58:6 68:0 73:6 83:0 89:6 99:0 105:6 115:0
|
||||
22: 0:0 42:6 52:0 58:6 68:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
23: 0:0 27:5 37:0 42:6 52:0 58:7 68:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
24: 0:0 27:6 37:0 42:6 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:6 115:0
|
||||
25: 0:0 27:6 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 27:7 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 16:4 17:0 27:7 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 7:2 8:0 9:4 118:0
|
||||
17: 0:0 57:5 67:0 89:6 99:0 105:3 113:0
|
||||
18: 0:0 3:2 7:0 57:5 67:0 73:5 83:0 89:6 99:0
|
||||
19: 0:0 57:6 67:0 73:5 83:0 89:6 99:0
|
||||
20: 0:0 57:6 67:0 73:6 83:0 89:6 99:0 105:5 115:0
|
||||
21: 0:0 42:5 52:0 57:6 67:0 73:6 83:0 89:6 99:0 105:6 115:0
|
||||
22: 0:0 42:6 52:0 57:6 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
23: 0:0 26:5 36:0 42:6 52:0 57:7 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
24: 0:0 26:6 36:0 42:6 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:6 115:0
|
||||
25: 0:0 26:6 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 15:4 16:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 6:2 7:0 8:4 118:0
|
||||
29: 0:0 109:3 110:0
|
||||
30: 0:0 13:2 19:0 28:2 34:0 44:2 50:0 59:2 65:0 75:2 81:0 91:2 96:0 107:8 112:0
|
||||
30: 0:0 12:2 18:0 27:2 33:0 43:2 49:0 59:2 65:0 74:2 80:0 91:2 96:0 107:8 112:0
|
||||
31: 0:0
|
||||
32: 0:0 2:4 58:0
|
||||
33: 0:0 2:1 7:3 26:0
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ expression: snapshot
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -56,7 +56,7 @@ Palette (foreground|background|underline|modifiers):
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:2 83:3 96:0
|
||||
2: 0:0 2:2 94:3 107:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 13:0
|
||||
5: 0:0 2:4 118:0
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 68:3 85:2 96:0
|
||||
3: 0:0 2:2 79:3 96:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 15:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
1: Reset|Reset|Reset|BOLD
|
||||
2: Rgb(165, 165, 165)|Reset|Reset|NONE
|
||||
3: Cyan|Reset|Reset|BOLD
|
||||
4: Reset|Reset|Reset|DIM
|
||||
5: White|Reset|Reset|BOLD
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:3 15:2 107:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 11:0
|
||||
5: 0:0 2:4 118:0
|
||||
6: 0:0
|
||||
7: 0:0
|
||||
8: 0:0
|
||||
9: 0:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
13: 0:0
|
||||
14: 0:0
|
||||
15: 0:0
|
||||
16: 0:0
|
||||
17: 0:0
|
||||
18: 0:0
|
||||
19: 0:0
|
||||
20: 0:0
|
||||
21: 0:0
|
||||
22: 0:0
|
||||
23: 0:0
|
||||
24: 0:0
|
||||
25: 0:0
|
||||
26: 0:0
|
||||
27: 0:0
|
||||
28: 0:0
|
||||
29: 0:0
|
||||
30: 0:0
|
||||
31: 0:0
|
||||
32: 0:0
|
||||
33: 0:0
|
||||
34: 0:0
|
||||
35: 0:0
|
||||
36: 0:0
|
||||
37: 0:0
|
||||
38: 0:0
|
||||
39: 0:0
|
||||
40: 0:0 2:5 9:2 20:5 21:2 34:5 37:2 118:0
|
||||
41: 0:0 2:5 3:2 11:5 12:2 23:5 32:2 42:5 45:2 53:5 54:2 118:0
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Total usage history] 2 Messages 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Total usage history] 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Total usage history By feature "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● Images 11.6% "
|
||||
" ● Other 10.5% "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:3 27:2 83:0
|
||||
3: 0:0 2:2 13:3 38:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 23:0 108:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 37:3 57:2 83:0
|
||||
3: 0:0 2:2 48:3 68:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 18:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,13 +1,12 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -57,7 +56,7 @@ Palette (foreground|background|underline|modifiers):
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:2 70:3 83:0
|
||||
2: 0:0 2:2 81:3 94:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 13:0
|
||||
5: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history [2 Messages] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history [3 Messages] 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Messages By model "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● GPT-5.5 10 "
|
||||
" ● Other 7 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 25:3 39:2 83:0
|
||||
3: 0:0 2:2 36:3 50:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 12:0 110:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 55:3 72:2 83:0
|
||||
3: 0:0 2:2 66:3 83:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 15:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
1: Reset|Reset|Reset|BOLD
|
||||
2: Rgb(165, 165, 165)|Reset|Reset|NONE
|
||||
3: Cyan|Reset|Reset|BOLD
|
||||
4: Reset|Reset|Reset|DIM
|
||||
5: White|Reset|Reset|BOLD
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:3 15:2 94:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 11:0
|
||||
5: 0:0 2:4 118:0
|
||||
6: 0:0
|
||||
7: 0:0
|
||||
8: 0:0
|
||||
9: 0:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
13: 0:0
|
||||
14: 0:0
|
||||
15: 0:0
|
||||
16: 0:0
|
||||
17: 0:0
|
||||
18: 0:0
|
||||
19: 0:0
|
||||
20: 0:0
|
||||
21: 0:0
|
||||
22: 0:0
|
||||
23: 0:0
|
||||
24: 0:0
|
||||
25: 0:0
|
||||
26: 0:0
|
||||
27: 0:0
|
||||
28: 0:0
|
||||
29: 0:0
|
||||
30: 0:0
|
||||
31: 0:0
|
||||
32: 0:0
|
||||
33: 0:0
|
||||
34: 0:0
|
||||
35: 0:0
|
||||
36: 0:0
|
||||
37: 0:0
|
||||
38: 0:0
|
||||
39: 0:0
|
||||
40: 0:0 2:5 9:2 20:5 21:2 34:5 37:2 118:0
|
||||
41: 0:0 2:5 3:2 11:5 12:2 23:5 32:2 42:5 45:2 53:5 54:2 118:0
|
||||
+45
-45
@@ -7,34 +7,34 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history [2 Token usage history] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history [3 Token usage history] 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Token usage history By token type "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" All models · m change model "
|
||||
" 4,377,500 tokens "
|
||||
" 4.4M tokens "
|
||||
" "
|
||||
" 1,000,000 "
|
||||
" 1M "
|
||||
" "
|
||||
" ▂▂▂▂▂▂▂▂▂▂ "
|
||||
" ██████████ 731,000 "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ ▁▁▁▁▁▁▁▁▁▁ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ▆▆▆▆▆▆▆▆▆▆ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 500,000 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" ▂▂▂▂▂▂▂▂▂▂ "
|
||||
" ██████████ 731K "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ ▁▁▁▁▁▁▁▁▁▁ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ▆▆▆▆▆▆▆▆▆▆ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 500K ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 731,000 tokens "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● Output 43,000 "
|
||||
" Updated Sep 2 16:00 UTC "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,34 +61,34 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 27:3 52:2 96:0
|
||||
3: 0:0 2:2 38:3 63:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 23:0 105:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
7: 0:0 2:2 29:0
|
||||
8: 0:0 2:3 18:0
|
||||
8: 0:0 2:3 13:0
|
||||
9: 0:0
|
||||
10: 0:0 3:2 12:0
|
||||
10: 0:0 5:2 7:0
|
||||
11: 0:0
|
||||
12: 0:0 90:5 100:0
|
||||
13: 0:0 90:5 100:0 107:3 114:0
|
||||
14: 0:0 60:5 70:0 90:6 100:0 105:5 115:0
|
||||
15: 0:0 60:6 70:0 75:5 85:0 90:7 100:0 105:5 115:0
|
||||
16: 0:0 30:5 40:0 60:6 70:0 75:6 85:0 90:7 100:0 105:6 115:0
|
||||
17: 0:0 30:6 40:0 60:7 70:0 75:6 85:0 90:7 100:0 105:7 115:0
|
||||
18: 0:0 30:6 40:0 45:5 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
19: 0:0 5:2 12:0 30:7 40:0 45:6 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
20: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
21: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
22: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
23: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
24: 0:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
25: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
26: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
27: 0:0 15:7 25:0 30:7 40:0 45:7 55:0 60:7 70:0 75:7 85:0 90:7 100:0 105:7 115:0
|
||||
28: 0:0 11:2 12:0 13:4 118:0
|
||||
29: 0:0 110:3 111:0
|
||||
30: 0:0 17:2 23:0 32:2 38:0 47:2 53:0 62:2 68:0 77:2 83:0 93:2 98:0 108:8 113:0
|
||||
12: 0:0 89:5 99:0
|
||||
13: 0:0 89:5 99:0 107:3 111:0
|
||||
14: 0:0 57:5 67:0 89:6 99:0 105:5 115:0
|
||||
15: 0:0 57:6 67:0 73:5 83:0 89:7 99:0 105:5 115:0
|
||||
16: 0:0 26:5 36:0 57:6 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
17: 0:0 26:6 36:0 57:7 67:0 73:6 83:0 89:7 99:0 105:7 115:0
|
||||
18: 0:0 26:6 36:0 42:5 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
19: 0:0 3:2 7:0 26:7 36:0 42:6 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
20: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
21: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
22: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
23: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
24: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
25: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 10:7 20:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 6:2 7:0 8:4 118:0
|
||||
29: 0:0 109:3 110:0
|
||||
30: 0:0 12:2 18:0 27:2 33:0 43:2 49:0 59:2 65:0 74:2 80:0 91:2 96:0 107:8 112:0
|
||||
31: 0:0
|
||||
32: 0:0 2:4 58:0
|
||||
33: 0:0 2:1 7:3 24:0
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 50:3 70:2 96:0
|
||||
3: 0:0 2:2 61:3 81:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 18:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+35
-35
@@ -7,34 +7,34 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Credits usage history] 2 Token usage history 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Credits usage history] 3 Token usage history 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Credits usage history By product "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" 17,240.00 credits "
|
||||
" 17.2K credits "
|
||||
" "
|
||||
" 5,000 "
|
||||
" 5K "
|
||||
" "
|
||||
" ▄▄▄▄▄▄▄▄▄▄ "
|
||||
" ██████████ "
|
||||
" ██████████ "
|
||||
" ██████████ "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ "
|
||||
" ██████████ ██████████ ██████████ 2,560.00 "
|
||||
" 2,500 ██████████ ██████████ ██████████ ▅▅▅▅▅▅▅▅▅▅ "
|
||||
" ▅▅▅▅▅▅▅▅▅▅ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ───────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▇▇▇▇▇▇▇▇▇▇ ██████████ "
|
||||
" ██████████ ▃▃▃▃▃▃▃▃▃▃ ██████████ "
|
||||
" ██████████ ██████████ ██████████ 2,560.00 "
|
||||
" 2.5K ██████████ ██████████ ██████████ ▅▅▅▅▅▅▅▅▅▅ "
|
||||
" ▅▅▅▅▅▅▅▅▅▅ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ▁▁▁▁▁▁▁▁▁▁ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ██████████ ██████████ ██████████ ██████████ ██████████ ██████████ "
|
||||
" 0 ────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" ▲ "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" Aug 27 Aug 28 Aug 29 Aug 30 Aug 31 Sep 1 Sep 2 "
|
||||
" "
|
||||
" ──────────────────────────────────────────────────────── "
|
||||
" Sep 2 · 2,560.00 credits "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● IDE extension 400.00 "
|
||||
" ● Web 360.00 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,13 +61,13 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:3 29:2 96:0
|
||||
3: 0:0 2:2 13:3 40:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 25:0 108:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
7: 0:0 2:3 19:0
|
||||
7: 0:0 2:3 15:0
|
||||
8: 0:0
|
||||
9: 0:0 3:2 8:0
|
||||
9: 0:0 5:2 7:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
@@ -75,20 +75,20 @@ Rows (column:palette):
|
||||
14: 0:0 89:5 99:0
|
||||
15: 0:0 89:5 99:0
|
||||
16: 0:0 89:6 99:0
|
||||
17: 0:0 58:5 68:0 89:6 99:0 105:3 113:0
|
||||
18: 0:0 3:2 8:0 58:5 68:0 73:5 83:0 89:6 99:0
|
||||
19: 0:0 58:6 68:0 73:5 83:0 89:6 99:0
|
||||
20: 0:0 58:6 68:0 73:6 83:0 89:6 99:0 105:5 115:0
|
||||
21: 0:0 42:5 52:0 58:6 68:0 73:6 83:0 89:6 99:0 105:6 115:0
|
||||
22: 0:0 42:6 52:0 58:6 68:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
23: 0:0 27:5 37:0 42:6 52:0 58:7 68:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
24: 0:0 27:6 37:0 42:6 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:6 115:0
|
||||
25: 0:0 27:6 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 27:7 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 16:4 17:0 27:7 37:0 42:7 52:0 58:7 68:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 7:2 8:0 9:4 118:0
|
||||
17: 0:0 57:5 67:0 89:6 99:0 105:3 113:0
|
||||
18: 0:0 3:2 7:0 57:5 67:0 73:5 83:0 89:6 99:0
|
||||
19: 0:0 57:6 67:0 73:5 83:0 89:6 99:0
|
||||
20: 0:0 57:6 67:0 73:6 83:0 89:6 99:0 105:5 115:0
|
||||
21: 0:0 42:5 52:0 57:6 67:0 73:6 83:0 89:6 99:0 105:6 115:0
|
||||
22: 0:0 42:6 52:0 57:6 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
23: 0:0 26:5 36:0 42:6 52:0 57:7 67:0 73:6 83:0 89:7 99:0 105:6 115:0
|
||||
24: 0:0 26:6 36:0 42:6 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:6 115:0
|
||||
25: 0:0 26:6 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
26: 0:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
27: 0:0 15:4 16:0 26:7 36:0 42:7 52:0 57:7 67:0 73:7 83:0 89:7 99:0 105:7 115:0
|
||||
28: 0:0 6:2 7:0 8:4 118:0
|
||||
29: 0:0 109:3 110:0
|
||||
30: 0:0 13:2 19:0 28:2 34:0 44:2 50:0 59:2 65:0 75:2 81:0 91:2 96:0 107:8 112:0
|
||||
30: 0:0 12:2 18:0 27:2 33:0 43:2 49:0 59:2 65:0 74:2 80:0 91:2 96:0 107:8 112:0
|
||||
31: 0:0
|
||||
32: 0:0 2:4 58:0
|
||||
33: 0:0 2:1 7:3 26:0
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ expression: snapshot
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -56,7 +56,7 @@ Palette (foreground|background|underline|modifiers):
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:2 83:3 96:0
|
||||
2: 0:0 2:2 94:3 107:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 13:0
|
||||
5: 0:0 2:4 118:0
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Credits usage history 2 Token usage history 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Credits usage history 3 Token usage history 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -44,7 +44,7 @@ Text:
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -61,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 68:3 85:2 96:0
|
||||
3: 0:0 2:2 79:3 96:2 107:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 15:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Credits usage history 3 Token usage history 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
1: Reset|Reset|Reset|BOLD
|
||||
2: Rgb(76, 76, 76)|Reset|Reset|NONE
|
||||
3: Rgb(0, 95, 135)|Reset|Reset|BOLD
|
||||
4: Reset|Reset|Reset|DIM
|
||||
5: White|Reset|Reset|BOLD
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:3 15:2 107:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 11:0
|
||||
5: 0:0 2:4 118:0
|
||||
6: 0:0
|
||||
7: 0:0
|
||||
8: 0:0
|
||||
9: 0:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
13: 0:0
|
||||
14: 0:0
|
||||
15: 0:0
|
||||
16: 0:0
|
||||
17: 0:0
|
||||
18: 0:0
|
||||
19: 0:0
|
||||
20: 0:0
|
||||
21: 0:0
|
||||
22: 0:0
|
||||
23: 0:0
|
||||
24: 0:0
|
||||
25: 0:0
|
||||
26: 0:0
|
||||
27: 0:0
|
||||
28: 0:0
|
||||
29: 0:0
|
||||
30: 0:0
|
||||
31: 0:0
|
||||
32: 0:0
|
||||
33: 0:0
|
||||
34: 0:0
|
||||
35: 0:0
|
||||
36: 0:0
|
||||
37: 0:0
|
||||
38: 0:0
|
||||
39: 0:0
|
||||
40: 0:0 2:5 9:2 20:5 21:2 34:5 37:2 118:0
|
||||
41: 0:0 2:5 3:2 11:5 12:2 23:5 32:2 42:5 45:2 53:5 54:2 118:0
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" [1 Total usage history] 2 Messages 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary [2 Total usage history] 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Total usage history By feature "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● Images 11.6% "
|
||||
" ● Other 10.5% "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:3 27:2 83:0
|
||||
3: 0:0 2:2 13:3 38:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 23:0 108:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 37:3 57:2 83:0
|
||||
3: 0:0 2:2 48:3 68:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 18:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,13 +1,12 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called 4 Skills used [5 Top chats] "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called 5 Skills used [6 Top chats] "
|
||||
" "
|
||||
" ▎ Top chats "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" tab/1–6 section · s sort · z dashboard · ↑/↓ row · enter details "
|
||||
" R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -57,7 +56,7 @@ Palette (foreground|background|underline|modifiers):
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:2 70:3 83:0
|
||||
2: 0:0 2:2 81:3 94:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 13:0
|
||||
5: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history [2 Messages] 3 Plugins called 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history [3 Messages] 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Messages By model "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● GPT-5.5 10 "
|
||||
" ● Other 7 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · g group · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 25:3 39:2 83:0
|
||||
3: 0:0 2:2 36:3 50:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 12:0 110:2 118:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
assertion_line: 142
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
@@ -8,7 +7,7 @@ Text:
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,7 +44,7 @@ Text:
|
||||
" ● Planning 3 "
|
||||
" ● Debugging 1 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
@@ -62,7 +61,7 @@ Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0 2:3 6:0 11:2 34:0
|
||||
2: 0:0
|
||||
3: 0:0 2:2 55:3 72:2 83:0
|
||||
3: 0:0 2:2 66:3 83:2 94:0
|
||||
4: 0:0
|
||||
5: 0:0 2:3 15:0
|
||||
6: 0:0 2:4 118:0
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
source: tui/src/analytics/styles_tests.rs
|
||||
expression: snapshot
|
||||
---
|
||||
120x42
|
||||
Text:
|
||||
" Analytics · Live account "
|
||||
" "
|
||||
" [1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Summary "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
" No history has been reported. "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–6 section · z dashboard · ↑/↓ scroll "
|
||||
" g view · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
Palette (foreground|background|underline|modifiers):
|
||||
0: Reset|Reset|Reset|NONE
|
||||
1: Reset|Reset|Reset|BOLD
|
||||
2: Rgb(76, 76, 76)|Reset|Reset|NONE
|
||||
3: Rgb(0, 95, 135)|Reset|Reset|BOLD
|
||||
4: Reset|Reset|Reset|DIM
|
||||
5: White|Reset|Reset|BOLD
|
||||
Rows (column:palette):
|
||||
0: 0:0 2:1 11:2 28:0
|
||||
1: 0:0
|
||||
2: 0:0 2:3 15:2 94:0
|
||||
3: 0:0
|
||||
4: 0:0 2:3 11:0
|
||||
5: 0:0 2:4 118:0
|
||||
6: 0:0
|
||||
7: 0:0
|
||||
8: 0:0
|
||||
9: 0:0
|
||||
10: 0:0
|
||||
11: 0:0
|
||||
12: 0:0
|
||||
13: 0:0
|
||||
14: 0:0
|
||||
15: 0:0
|
||||
16: 0:0
|
||||
17: 0:0
|
||||
18: 0:0
|
||||
19: 0:0
|
||||
20: 0:0
|
||||
21: 0:0
|
||||
22: 0:0
|
||||
23: 0:0
|
||||
24: 0:0
|
||||
25: 0:0
|
||||
26: 0:0
|
||||
27: 0:0
|
||||
28: 0:0
|
||||
29: 0:0
|
||||
30: 0:0
|
||||
31: 0:0
|
||||
32: 0:0
|
||||
33: 0:0
|
||||
34: 0:0
|
||||
35: 0:0
|
||||
36: 0:0
|
||||
37: 0:0
|
||||
38: 0:0
|
||||
39: 0:0
|
||||
40: 0:0 2:5 9:2 20:5 21:2 34:5 37:2 118:0
|
||||
41: 0:0 2:5 3:2 11:5 12:2 23:5 32:2 42:5 45:2 53:5 54:2 118:0
|
||||
+6
-7
@@ -1,12 +1,11 @@
|
||||
---
|
||||
source: tui/src/analytics/tool_panel_tests.rs
|
||||
assertion_line: 78
|
||||
expression: "format!(\"{empty}\\n{missing}\\n{partial}\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -45,13 +44,13 @@ expression: "format!(\"{empty}\\n{missing}\\n{partial}\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day "
|
||||
" tab/1–6 section · z dashboard · ←/→ day "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -90,13 +89,13 @@ expression: "format!(\"{empty}\\n{missing}\\n{partial}\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day "
|
||||
" tab/1–6 section · z dashboard · ←/→ day "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages 3 Plugins called [4 Skills used] 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages 4 Plugins called [5 Skills used] 6 Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -135,5 +134,5 @@ expression: "format!(\"{empty}\\n{missing}\\n{partial}\")"
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day "
|
||||
" tab/1–6 section · z dashboard · ←/→ day "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
+5
-6
@@ -1,12 +1,11 @@
|
||||
---
|
||||
source: tui/src/analytics/tool_panel_tests.rs
|
||||
assertion_line: 46
|
||||
expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
---
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" 1 Total usage history 2 Messages [3 Plugins called] 4 Skills used 5 Top chats "
|
||||
" 1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills used 6 Top chats "
|
||||
" "
|
||||
" ▎ Plugins called "
|
||||
" ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── "
|
||||
@@ -49,14 +48,14 @@ expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
" ● GitHub 5 "
|
||||
" ● Notion 4 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter details "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close "
|
||||
|
||||
" Analytics · Live account "
|
||||
" [7d] 30d · Aug 27–Sep 2, 2026 "
|
||||
" "
|
||||
" Total usage history Messages Plugins called [Skills "
|
||||
" used] Top chats "
|
||||
" Summary Total usage history Messages Plugins called "
|
||||
" [Skills used] Top chats "
|
||||
" "
|
||||
" ▎ Skills used "
|
||||
" ────────────────────────────────────────────────────── "
|
||||
@@ -80,7 +79,7 @@ expression: "format!(\"{wide}\\n{narrow}\")"
|
||||
" ● Planning 1 "
|
||||
" ● Debugging 0 "
|
||||
" "
|
||||
" tab/1–5 section · z dashboard · ←/→ day · enter "
|
||||
" tab/1–6 section · z dashboard · ←/→ day · enter "
|
||||
" details "
|
||||
" r 7/30d · R refresh · pgup/pgdn scroll · esc back · q "
|
||||
" close "
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Account profile loading and the Summary's year-view interaction.
|
||||
//! Aggregation changes reuse the loaded profile; account refresh replaces all profile data.
|
||||
|
||||
use super::AnalyticsView;
|
||||
use super::activity_chart::TokenActivityView;
|
||||
use super::client::Live;
|
||||
use super::data::Load;
|
||||
use super::sections::Section;
|
||||
use crate::keymap::ListAction;
|
||||
use codex_backend_client::AccountProfile;
|
||||
|
||||
pub(super) const VIEWS: [TokenActivityView; 3] = [
|
||||
TokenActivityView::Daily,
|
||||
TokenActivityView::Weekly,
|
||||
TokenActivityView::Cumulative,
|
||||
];
|
||||
|
||||
impl Live {
|
||||
pub(super) async fn profile(&self) -> Result<Option<AccountProfile>, String> {
|
||||
let session = self.session().await?;
|
||||
let profile = session
|
||||
.backend
|
||||
.request(|client| async move { client.get_account_profile().await })
|
||||
.await;
|
||||
session.backend.ensure_identity().await?;
|
||||
profile.map(Some).map_err(super::client::request_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnalyticsView {
|
||||
pub(crate) fn select_summary(&mut self, view: Option<TokenActivityView>) {
|
||||
self.section = Section::Summary;
|
||||
self.zoomed = true;
|
||||
self.scroll_offset = 0;
|
||||
self.group_picker = None;
|
||||
if let Some(view) = view {
|
||||
self.sections[Section::Summary].group = VIEWS
|
||||
.iter()
|
||||
.position(|item| *item == view)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn load_summary(&mut self) {
|
||||
if let (Some((_, _, frame)), Some(live)) = (&self.connection, &self.live) {
|
||||
let live = std::sync::Arc::clone(live);
|
||||
self.profile = Load::start(async move { live.profile().await }, frame.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn summary_action(&mut self, action: Option<ListAction>) {
|
||||
let step = match action {
|
||||
Some(ListAction::MoveUp) => Some(-1),
|
||||
Some(ListAction::MoveDown) => Some(1),
|
||||
Some(ListAction::PageUp) => Some(-(self.viewport_height as isize)),
|
||||
Some(ListAction::PageDown) => Some(self.viewport_height as isize),
|
||||
Some(ListAction::JumpTop) => {
|
||||
self.scroll_offset = 0;
|
||||
Some(0)
|
||||
}
|
||||
Some(ListAction::JumpBottom) => {
|
||||
self.scroll_offset = usize::MAX;
|
||||
Some(0)
|
||||
}
|
||||
Some(ListAction::Cancel) => {
|
||||
self.is_done = true;
|
||||
None
|
||||
}
|
||||
Some(ListAction::MoveLeft | ListAction::MoveRight | ListAction::Accept) | None => None,
|
||||
};
|
||||
if let Some(step) = step {
|
||||
self.scroll_offset = self.scroll_offset.saturating_add_signed(step);
|
||||
self.follow_selection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
//! Responsive profile summary using the Analytics container's typography and spacing.
|
||||
|
||||
use super::AnalyticsView;
|
||||
use super::activity_chart;
|
||||
use super::render::columns;
|
||||
use super::render::join_columns;
|
||||
use super::sections::Section;
|
||||
use super::styles::secondary_style;
|
||||
use crate::line_truncation::truncate_line_with_ellipsis_if_overflow as truncate;
|
||||
use crate::style::accent_style;
|
||||
use crate::wrapping::RtOptions;
|
||||
use crate::wrapping::word_wrap_lines;
|
||||
use codex_backend_client::ProfileInvocationKind;
|
||||
use ratatui::style::Styled;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
|
||||
impl AnalyticsView {
|
||||
pub(super) fn summary_lines(&self, width: usize) -> Vec<Line<'static>> {
|
||||
let width = width.max(/*other*/ 1);
|
||||
let inner_width = width.min(/*other*/ 110);
|
||||
let mut lines = Vec::new();
|
||||
let Some(profile) = self.profile.ready() else {
|
||||
lines.push(
|
||||
self.profile
|
||||
.message()
|
||||
.unwrap_or("Profile statistics unavailable.")
|
||||
.to_owned()
|
||||
.into(),
|
||||
);
|
||||
return lines;
|
||||
};
|
||||
if let Some(identity) = &profile.profile {
|
||||
if let Some(name) = identity
|
||||
.display_name
|
||||
.as_deref()
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
{
|
||||
lines.push(center(
|
||||
truncate(name.trim().to_owned().bold().into(), inner_width),
|
||||
inner_width,
|
||||
));
|
||||
}
|
||||
if let Some(username) = identity
|
||||
.username
|
||||
.as_deref()
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
{
|
||||
lines.push(center(
|
||||
truncate(
|
||||
format!("@{}", username.trim().trim_start_matches('@'))
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
inner_width,
|
||||
),
|
||||
inner_width,
|
||||
));
|
||||
}
|
||||
}
|
||||
let plan = self.account.ready().map(|plan| {
|
||||
use codex_protocol::account::PlanType;
|
||||
match plan {
|
||||
PlanType::Free => "Free",
|
||||
PlanType::Go => "Go",
|
||||
PlanType::Plus => "Plus",
|
||||
PlanType::Pro => "Pro",
|
||||
PlanType::ProLite => "Pro Lite",
|
||||
PlanType::Team
|
||||
| PlanType::Business
|
||||
| PlanType::SelfServeBusinessProLite
|
||||
| PlanType::SelfServeBusinessUsageBased => "Business",
|
||||
PlanType::Ent26
|
||||
| PlanType::EnterpriseCbpAutomation
|
||||
| PlanType::EnterpriseCbpUsageBased
|
||||
| PlanType::Enterprise => "Enterprise",
|
||||
PlanType::Edu | PlanType::EduPlus | PlanType::EduPro => "Education",
|
||||
PlanType::Unknown => "Account",
|
||||
}
|
||||
});
|
||||
if let Some(plan) = plan {
|
||||
lines.push(center(
|
||||
plan.set_style(secondary_style()).into(),
|
||||
inner_width,
|
||||
));
|
||||
}
|
||||
lines.push(Line::default());
|
||||
let tokens = &profile.stats.tokens;
|
||||
let fields = [
|
||||
(
|
||||
"Lifetime tokens",
|
||||
tokens
|
||||
.lifetime_tokens
|
||||
.map(|value| super::data::compact_amount(value as f64)),
|
||||
),
|
||||
(
|
||||
"Peak tokens",
|
||||
tokens
|
||||
.peak_daily_tokens
|
||||
.map(|value| super::data::compact_amount(value as f64)),
|
||||
),
|
||||
(
|
||||
"Longest chat",
|
||||
tokens.longest_running_turn_sec.map(duration),
|
||||
),
|
||||
(
|
||||
"Current streak",
|
||||
tokens
|
||||
.current_streak_days
|
||||
.map(|days| format!("{days} days")),
|
||||
),
|
||||
(
|
||||
"Longest streak",
|
||||
tokens
|
||||
.longest_streak_days
|
||||
.map(|days| format!("{days} days")),
|
||||
),
|
||||
];
|
||||
if !self.zoomed {
|
||||
lines.extend(fields.into_iter().map(|(label, value)| {
|
||||
columns(
|
||||
label.set_style(secondary_style()).into(),
|
||||
value.unwrap_or_else(|| "—".into()).bold().into(),
|
||||
inner_width,
|
||||
)
|
||||
}));
|
||||
return lines;
|
||||
}
|
||||
let count = if inner_width >= 90 {
|
||||
5
|
||||
} else if inner_width >= 54 {
|
||||
3
|
||||
} else if inner_width >= 36 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for chunk in fields.chunks(count) {
|
||||
let cell_width = inner_width / chunk.len();
|
||||
let mut values = Line::default();
|
||||
let mut labels = Line::default();
|
||||
for (index, (label, value)) in chunk.iter().enumerate() {
|
||||
for (row, cell) in [
|
||||
(
|
||||
&mut values,
|
||||
value.clone().unwrap_or_else(|| "—".into()).bold(),
|
||||
),
|
||||
(&mut labels, label.to_string().set_style(secondary_style())),
|
||||
] {
|
||||
let cell = center(truncate(cell.into(), cell_width), cell_width);
|
||||
let padding = cell_width.saturating_sub(cell.width());
|
||||
row.spans.extend(cell.spans);
|
||||
if index + 1 < chunk.len() {
|
||||
row.spans.push(" ".repeat(padding).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.extend([values, labels, Line::default()]);
|
||||
}
|
||||
let selected = self.sections[Section::Summary].group;
|
||||
let mut controls = Line::default();
|
||||
for (index, view) in super::summary::VIEWS.iter().enumerate() {
|
||||
if index > 0 {
|
||||
controls.spans.push(" ".into());
|
||||
}
|
||||
controls.spans.push(if index == selected {
|
||||
format!("[{}]", view.label()).set_style(accent_style())
|
||||
} else {
|
||||
view.label().to_string().set_style(secondary_style())
|
||||
});
|
||||
}
|
||||
if inner_width >= 58 {
|
||||
lines.push(columns(
|
||||
"Token activity".bold().into(),
|
||||
controls,
|
||||
inner_width,
|
||||
));
|
||||
} else {
|
||||
lines.push("Token activity".bold().into());
|
||||
lines.extend(word_wrap_lines([controls], RtOptions::new(inner_width)));
|
||||
}
|
||||
lines.push("Last 12 months".set_style(secondary_style()).into());
|
||||
lines.push(Line::default());
|
||||
if let Some(buckets) = &tokens.daily_usage_buckets {
|
||||
lines.extend(activity_chart::chart_lines(
|
||||
super::summary::VIEWS[selected],
|
||||
buckets,
|
||||
self.end_date,
|
||||
inner_width as u16,
|
||||
));
|
||||
} else {
|
||||
lines.push(
|
||||
"Token activity history unavailable"
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
lines.push(Line::default());
|
||||
let stats = &profile.stats;
|
||||
let reasoning = stats
|
||||
.most_used_reasoning_effort
|
||||
.as_deref()
|
||||
.map(|effort| {
|
||||
let percentage = percent(stats.most_used_reasoning_effort_percentage.as_ref());
|
||||
format!("{effort} · {percentage}")
|
||||
})
|
||||
.unwrap_or_else(|| "—".into());
|
||||
let metrics = [
|
||||
(
|
||||
"Fast Mode",
|
||||
percent(stats.fast_mode_usage_percentage.as_ref()),
|
||||
),
|
||||
("Most used reasoning", reasoning),
|
||||
("Skills explored", count_value(stats.unique_skills_used)),
|
||||
("Total skills used", count_value(stats.total_skills_used)),
|
||||
("Total chats", count_value(stats.total_threads)),
|
||||
];
|
||||
let two_columns = inner_width >= 76;
|
||||
let column_width = if two_columns {
|
||||
(inner_width - 4) / 2
|
||||
} else {
|
||||
inner_width
|
||||
};
|
||||
let mut insights = vec!["Activity insights".bold().into(), Line::default()];
|
||||
for (label, value) in metrics {
|
||||
insights.extend(word_wrap_lines(
|
||||
[columns(
|
||||
label.set_style(secondary_style()).into(),
|
||||
value.into(),
|
||||
column_width,
|
||||
)],
|
||||
RtOptions::new(column_width),
|
||||
));
|
||||
}
|
||||
let mut plugins = vec![
|
||||
"Most used plugins and skills".bold().into(),
|
||||
Line::default(),
|
||||
];
|
||||
if let Some(invocations) = &stats.top_invocations {
|
||||
let mut displayed = 0;
|
||||
for invocation in invocations {
|
||||
let (prefix, name) = match invocation.kind {
|
||||
ProfileInvocationKind::Plugin => ("@", invocation.plugin_name.as_deref()),
|
||||
ProfileInvocationKind::Skill => ("$", invocation.skill_name.as_deref()),
|
||||
ProfileInvocationKind::Unknown => continue,
|
||||
};
|
||||
let Some(name) = name.filter(|name| !name.trim().is_empty()) else {
|
||||
continue;
|
||||
};
|
||||
let value = invocation
|
||||
.usage_count
|
||||
.map(|count| {
|
||||
format!(
|
||||
"{} {}",
|
||||
count_value(Some(count)),
|
||||
if count == 1 { "run" } else { "runs" }
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| "—".into());
|
||||
plugins.extend(word_wrap_lines(
|
||||
[columns(
|
||||
truncate(
|
||||
format!("{prefix}{}", name.trim()).into(),
|
||||
column_width.saturating_sub(value.len() + 1),
|
||||
),
|
||||
value.set_style(secondary_style()).into(),
|
||||
column_width,
|
||||
)],
|
||||
RtOptions::new(column_width),
|
||||
));
|
||||
displayed += 1;
|
||||
if displayed == 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if displayed == 0 {
|
||||
plugins.push(
|
||||
"No reported plugins or skills."
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
plugins.push(
|
||||
"Plugin and skill usage unavailable."
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if two_columns {
|
||||
lines.extend(join_columns(&insights, &plugins, column_width));
|
||||
} else {
|
||||
lines.extend(insights);
|
||||
lines.push(Line::default());
|
||||
lines.extend(plugins);
|
||||
}
|
||||
if let Some(metadata) = &profile.metadata {
|
||||
lines.push(Line::default());
|
||||
if let Some(date) = metadata
|
||||
.stats_as_of
|
||||
.as_deref()
|
||||
.and_then(|date| chrono::NaiveDate::parse_from_str(date, "%Y-%m-%d").ok())
|
||||
{
|
||||
lines.push(
|
||||
format!("Statistics as of {}", date.format("%b %-d, %Y"))
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if metadata
|
||||
.stats_error
|
||||
.as_deref()
|
||||
.is_some_and(|error| !error.trim().is_empty())
|
||||
{
|
||||
lines.push(
|
||||
"Some profile statistics are unavailable."
|
||||
.set_style(secondary_style())
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
let padding = " ".repeat(width.saturating_sub(inner_width) / 2);
|
||||
word_wrap_lines(lines, RtOptions::new(inner_width))
|
||||
.into_iter()
|
||||
.map(|mut line| {
|
||||
line.spans.insert(/*index*/ 0, padding.clone().into());
|
||||
line
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn center(mut line: Line<'static>, width: usize) -> Line<'static> {
|
||||
line.spans.insert(
|
||||
/*index*/ 0,
|
||||
" ".repeat(width.saturating_sub(line.width()) / 2).into(),
|
||||
);
|
||||
line
|
||||
}
|
||||
|
||||
fn percent(value: Option<&serde_json::Number>) -> String {
|
||||
value
|
||||
.and_then(serde_json::Number::as_f64)
|
||||
.filter(|value| value.is_finite() && (0.0..=100.0).contains(value))
|
||||
.map(|value| format!("{value:.0}%"))
|
||||
.unwrap_or_else(|| "—".into())
|
||||
}
|
||||
|
||||
fn count_value(value: Option<u64>) -> String {
|
||||
let Some(value) = value else {
|
||||
return "—".into();
|
||||
};
|
||||
let digits = value.to_string();
|
||||
digits
|
||||
.chars()
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut result, (index, ch)| {
|
||||
if index > 0 && (digits.len() - index).is_multiple_of(/*rhs*/ 3) {
|
||||
result.push(',');
|
||||
}
|
||||
result.push(ch);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn duration(seconds: i64) -> String {
|
||||
let seconds = seconds.max(/*other*/ 0);
|
||||
let (hours, minutes) = (seconds / 3600, seconds % 3600 / 60);
|
||||
match (hours, minutes) {
|
||||
(0, 0) => format!("{seconds}s"),
|
||||
(0, minutes) => format!("{minutes}m"),
|
||||
(hours, 0) => format!("{hours}h"),
|
||||
(hours, minutes) => format!("{hours}h {minutes}m"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//! Summary reflow, aggregation selection, and independently unavailable profile sections.
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
fn profile() -> codex_backend_client::AccountProfile {
|
||||
serde_json::from_value(json!({
|
||||
"profile":{"display_name":"Example User","username":"example.user"},
|
||||
"metadata":{"stats_as_of":"2026-09-09"},
|
||||
"stats":{"lifetime_tokens":142_300_000_000_i64,"peak_daily_tokens":7_000_000_000_i64,
|
||||
"longest_running_turn_sec":158820,"current_streak_days":158,"longest_streak_days":160,
|
||||
"daily_usage_buckets":[{"start_date":"2026-09-08","tokens":100},{"start_date":"2026-09-09","tokens":200}],
|
||||
"fast_mode_usage_percentage":37.0,"most_used_reasoning_effort":"high","most_used_reasoning_effort_percentage":26.0,
|
||||
"unique_skills_used":147,"total_skills_used":13190,"total_threads":21482,
|
||||
"top_invocations":[{"type":"skill","skill_name":"review","usage_count":1142},{"type":"plugin","plugin_name":"GitHub","usage_count":1059},{"type":"future"}]}
|
||||
})).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn summary_modes_reflow_and_keep_selection_across_tabs() {
|
||||
let mut view = fixture::view(models::AccountKind::Consumer);
|
||||
view.profile = Load::Ready(profile());
|
||||
view.end_date = "2026-09-09".parse().unwrap();
|
||||
view.select_summary(Some(crate::analytics::TokenActivityView::Daily));
|
||||
insta::assert_snapshot!(
|
||||
"summary_daily_wide",
|
||||
screen(&mut view, /*width*/ 140, /*height*/ 44)
|
||||
);
|
||||
press(&mut view, KeyCode::Char('g'));
|
||||
press(&mut view, KeyCode::Down);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
assert_eq!(view.sections[Section::Summary].group, 1);
|
||||
assert!(view.profile.ready().is_some());
|
||||
insta::assert_snapshot!(
|
||||
"summary_weekly",
|
||||
screen(&mut view, /*width*/ 110, /*height*/ 44)
|
||||
);
|
||||
press(&mut view, KeyCode::Tab);
|
||||
press(&mut view, KeyCode::BackTab);
|
||||
assert_eq!(view.sections[Section::Summary].group, 1);
|
||||
press(&mut view, KeyCode::Char('g'));
|
||||
press(&mut view, KeyCode::Down);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
insta::assert_snapshot!(
|
||||
"summary_cumulative_narrow",
|
||||
screen(&mut view, /*width*/ 64, /*height*/ 60)
|
||||
);
|
||||
for width in [1, 4, 28, 64] {
|
||||
let lines = view.summary_lines(width);
|
||||
assert!(lines.iter().all(|line| line.width() <= width));
|
||||
}
|
||||
screen(&mut view, /*width*/ 64, /*height*/ 18);
|
||||
press(&mut view, KeyCode::End);
|
||||
insta::assert_snapshot!(
|
||||
"summary_scrolled",
|
||||
screen(&mut view, /*width*/ 64, /*height*/ 18)
|
||||
);
|
||||
press(&mut view, KeyCode::Home);
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
insta::assert_snapshot!(
|
||||
"summary_overview",
|
||||
screen(&mut view, /*width*/ 144, /*height*/ 64)
|
||||
);
|
||||
press(&mut view, KeyCode::Esc);
|
||||
assert!(view.is_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_summary_keeps_missing_values_and_zero() {
|
||||
let mut view = fixture::view(models::AccountKind::Business);
|
||||
view.select_summary(/*view*/ None);
|
||||
view.profile = Load::Ready(serde_json::from_value(json!({
|
||||
"metadata":{"stats_error":"sensitive upstream error"},
|
||||
"stats":{"lifetime_tokens":0,"fast_mode_usage_percentage":0,"total_threads":0,"top_invocations":[]}
|
||||
})).unwrap());
|
||||
let rendered = screen(&mut view, /*width*/ 100, /*height*/ 40);
|
||||
assert!(!rendered.contains("sensitive upstream error"));
|
||||
insta::assert_snapshot!("summary_partial", rendered);
|
||||
view.refresh();
|
||||
assert!(view.profile.ready().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn summary_loads_once_per_refresh_and_does_not_block_other_reports() {
|
||||
use wiremock::Mock;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::header;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
let server = test_support::server().await;
|
||||
let requests = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(/*v*/ 0));
|
||||
let count = std::sync::Arc::clone(&requests);
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/backend-api/wham/profiles/me"))
|
||||
.and(header("chatgpt-account-id", "account-a"))
|
||||
.respond_with(move |_: &wiremock::Request| {
|
||||
if count.fetch_add(/*val*/ 1, std::sync::atomic::Ordering::SeqCst) == 0 {
|
||||
ResponseTemplate::new(/*s*/ 503)
|
||||
} else {
|
||||
ResponseTemplate::new(/*s*/ 200)
|
||||
.set_body_json(json!({"stats":{"lifetime_tokens":123}}))
|
||||
}
|
||||
})
|
||||
.expect(/*r*/ 2)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, "plus").await;
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(view.section, Section::Summary);
|
||||
assert!(matches!(view.profile, Load::Error(_)));
|
||||
assert!(view.sections[Section::Usage].history.ready().is_some());
|
||||
press(&mut view, KeyCode::Char('g'));
|
||||
press(&mut view, KeyCode::Down);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
press(&mut view, KeyCode::Tab);
|
||||
press(&mut view, KeyCode::BackTab);
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(requests.load(std::sync::atomic::Ordering::SeqCst), 1);
|
||||
press(&mut view, KeyCode::Char('R'));
|
||||
test_support::settle(&mut view).await;
|
||||
assert_eq!(
|
||||
view.profile.ready().unwrap().stats.tokens.lifetime_tokens,
|
||||
Some(123)
|
||||
);
|
||||
assert_eq!(view.sections[Section::Summary].group, 1);
|
||||
view.cancel_loads();
|
||||
assert!(matches!(view.profile, Load::Unavailable));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn closing_summary_aborts_its_pending_profile() {
|
||||
let mut view = fixture::view(models::AccountKind::Consumer);
|
||||
let (cancelled_tx, cancelled_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
view.profile = Load::start(
|
||||
async move {
|
||||
let _cancelled = cancelled_tx;
|
||||
std::future::pending().await
|
||||
},
|
||||
crate::tui::FrameRequester::test_dummy(),
|
||||
);
|
||||
view.cancel_loads();
|
||||
assert!(cancelled_rx.await.is_err());
|
||||
}
|
||||
@@ -208,6 +208,11 @@ pub(super) fn view(kind: super::models::AccountKind) -> super::AnalyticsView {
|
||||
}
|
||||
super::models::AccountKind::Unknown => codex_protocol::account::PlanType::Unknown,
|
||||
});
|
||||
view.section = if view.business() {
|
||||
super::sections::Section::Credits
|
||||
} else {
|
||||
super::sections::Section::Usage
|
||||
};
|
||||
view.start_reports();
|
||||
seed_reports(&mut view);
|
||||
view
|
||||
|
||||
@@ -63,6 +63,7 @@ pub(super) async fn settle(view: &mut super::AnalyticsView) {
|
||||
.any(|state| matches!(state.history, Load::Loading(_)))
|
||||
&& !matches!(view.chats, Load::Loading(_))
|
||||
&& !matches!(view.tasks, Load::Loading(_))
|
||||
&& !matches!(view.profile, Load::Loading(_))
|
||||
&& !matches!(view.plan.report, Load::Loading(_))
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -7,10 +7,10 @@ use pretty_assertions::assert_eq;
|
||||
#[test]
|
||||
fn analytics_tools_reflow_and_keep_independent_days() {
|
||||
let mut view = fixture::view(models::AccountKind::Consumer);
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
let wide = screen(&mut view, /*width*/ 140, /*height*/ 48);
|
||||
assert!(wide.contains("186 calls"));
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Left);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
assert_eq!(
|
||||
@@ -56,7 +56,7 @@ fn analytics_tools_handle_empty_days_and_independent_failures() {
|
||||
}
|
||||
history.data.remove(/*index*/ 5);
|
||||
view.sections[Section::Skills].history = Load::Ready(history);
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
let empty = screen(&mut view, /*width*/ 140, /*height*/ 44);
|
||||
assert!(empty.contains("No uses reported for Sep 2."));
|
||||
assert!(!empty.contains("details"));
|
||||
@@ -73,7 +73,7 @@ fn analytics_tools_handle_empty_days_and_independent_failures() {
|
||||
Load::Error("Skills could not be loaded. Press R to retry.".into());
|
||||
let partial = screen(&mut view, /*width*/ 140, /*height*/ 44);
|
||||
assert!(partial.contains("Skills could not be loaded"));
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
assert!(screen(&mut view, /*width*/ 140, /*height*/ 44).contains("186 calls"));
|
||||
insta::assert_snapshot!(format!("{empty}\n{missing}\n{partial}"));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
//! Dashboard layout, local detail, shared ranges, grouping, and scroll/focus retention.
|
||||
|
||||
use crate::analytics::sections::Section;
|
||||
#[path = "analytics/compact_tests.rs"]
|
||||
mod compact;
|
||||
#[path = "analytics/dashboard_tests.rs"]
|
||||
mod dashboard;
|
||||
#[path = "analytics/summary_tests.rs"]
|
||||
mod summary;
|
||||
|
||||
#[path = "analytics/account_layout_tests.rs"]
|
||||
mod account_layout;
|
||||
@@ -50,7 +54,7 @@ fn analytics_overview() {
|
||||
fn analytics_narrow_charts_and_small_terminals_keep_valid_cursors() {
|
||||
let mut view = fixture::view(models::AccountKind::Consumer);
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
for section in ['1', '2', '3'] {
|
||||
for section in ['2', '3', '4'] {
|
||||
press(&mut view, KeyCode::Char(section));
|
||||
press(&mut view, KeyCode::Char('r'));
|
||||
fixture::seed_reports(&mut view);
|
||||
@@ -69,7 +73,7 @@ fn analytics_narrow_charts_and_small_terminals_keep_valid_cursors() {
|
||||
}
|
||||
let empty = ratatui::layout::Rect::default();
|
||||
view.render(empty, &mut ratatui::buffer::Buffer::empty(empty));
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
let tools = screen(&mut view, /*width*/ 58, /*height*/ 20);
|
||||
insta::assert_snapshot!(tools);
|
||||
}
|
||||
@@ -79,7 +83,7 @@ async fn analytics_credits_grouping_preserves_selected_day_and_total() {
|
||||
let server = test_support::server().await;
|
||||
let (_home, _app_server, mut view) = client::tests::connected_view(&server, "business").await;
|
||||
test_support::settle(&mut view).await;
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Left);
|
||||
let before = view.sections[Section::Credits]
|
||||
.history
|
||||
@@ -274,7 +278,7 @@ fn analytics_dates_distinguish_zero_missing_and_exact_details() {
|
||||
history.data[1].values.truncate(/*len*/ 1);
|
||||
history.data.remove(/*index*/ 2);
|
||||
view.sections[Section::Credits].history = Load::Ready(history.clone());
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Home);
|
||||
let mut screens = Vec::new();
|
||||
for keys in [
|
||||
@@ -334,11 +338,11 @@ fn analytics_model_labels_and_tool_remainders_are_unambiguous() {
|
||||
day.values[1].label = "Other".into();
|
||||
}
|
||||
view.sections[Section::Skills].history = Load::Ready(tools);
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
let overview = screen(&mut view, /*width*/ 140, /*height*/ 64);
|
||||
assert!(overview.contains("Friendly model"));
|
||||
assert!(overview.contains("Skills used"));
|
||||
press(&mut view, KeyCode::Char('4'));
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Enter);
|
||||
let skills = screen(&mut view, /*width*/ 100, /*height*/ 32);
|
||||
assert!(skills.contains("Other"));
|
||||
@@ -356,7 +360,7 @@ fn analytics_maximized_refunds_leave_room_for_both_bands() {
|
||||
value: -0.000004,
|
||||
}];
|
||||
view.sections[Section::Credits].history = Load::Ready(history);
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
let output = screen(&mut view, /*width*/ 100, /*height*/ 32);
|
||||
assert!(output.contains("Sep 2 · -0.000004 credits"));
|
||||
assert!(
|
||||
@@ -375,7 +379,7 @@ fn analytics_day_navigation_keeps_geometry_and_legend() {
|
||||
history.data[1].total = history.data[1].values[0].value;
|
||||
history.data.remove(/*index*/ 2);
|
||||
view.sections[Section::Credits].history = Load::Ready(history);
|
||||
press(&mut view, KeyCode::Char('1'));
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
let mut snapshots = Vec::new();
|
||||
for width in [58, 160] {
|
||||
let mut terminal = Terminal::new(TestBackend::new(width, /*height*/ 38)).unwrap();
|
||||
@@ -578,7 +582,7 @@ fn analytics_empty_turns_keep_geometry_and_disable_details() {
|
||||
}
|
||||
history.data.remove(/*index*/ 6);
|
||||
view.sections[Section::Activity].history = Load::Ready(history);
|
||||
press(&mut view, KeyCode::Char('2'));
|
||||
press(&mut view, KeyCode::Char('3'));
|
||||
let mut snapshots = Vec::new();
|
||||
for width in [58, 100] {
|
||||
view.sections[Section::Activity].cursor = 4;
|
||||
@@ -631,7 +635,7 @@ fn analytics_details_reflow_and_keep_focus() {
|
||||
let mut view = fixture::view(models::AccountKind::Enterprise);
|
||||
view.chats = Load::Ready(fixture::chats());
|
||||
press(&mut view, KeyCode::Char('z'));
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
press(&mut view, KeyCode::Enter);
|
||||
assert!(view.zoomed);
|
||||
press(&mut view, KeyCode::Enter);
|
||||
@@ -656,7 +660,7 @@ fn analytics_details_reflow_and_keep_focus() {
|
||||
before
|
||||
);
|
||||
press(&mut view, KeyCode::Tab);
|
||||
press(&mut view, KeyCode::Char('5'));
|
||||
press(&mut view, KeyCode::Char('6'));
|
||||
assert_eq!(
|
||||
(
|
||||
view.section,
|
||||
@@ -671,7 +675,7 @@ fn analytics_details_reflow_and_keep_focus() {
|
||||
view.is_done,
|
||||
view.sections.0.each_ref().map(|state| state.detail)
|
||||
),
|
||||
(false, [None; 7])
|
||||
(false, [None; 8])
|
||||
);
|
||||
press(&mut view, KeyCode::Esc);
|
||||
assert!(view.is_done);
|
||||
|
||||
@@ -30,8 +30,6 @@ use crate::hooks_rpc::write_hook_trust;
|
||||
use crate::hooks_rpc::write_hook_trusts;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
const TOKEN_ACTIVITY_FETCH_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_secs(/*secs*/ 15);
|
||||
pub(super) const THREAD_USAGE_FETCH_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_secs(/*secs*/ 65);
|
||||
const RATE_LIMIT_RESET_REQUEST_TIMEOUT: std::time::Duration =
|
||||
@@ -124,25 +122,6 @@ impl App {
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn refresh_token_activity(
|
||||
&mut self,
|
||||
app_server: &AppServerSession,
|
||||
request_id: u64,
|
||||
) {
|
||||
let request_handle = app_server.request_handle();
|
||||
let app_event_tx = self.app_event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = tokio::time::timeout(
|
||||
TOKEN_ACTIVITY_FETCH_TIMEOUT,
|
||||
fetch_account_token_activity(request_handle),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "account/usage/read timed out in TUI".to_string())
|
||||
.and_then(|result| result.map_err(|err| err.to_string()));
|
||||
app_event_tx.send(AppEvent::TokenActivityLoaded { request_id, result });
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn refresh_thread_usage(
|
||||
&mut self,
|
||||
app_server: &AppServerSession,
|
||||
@@ -831,19 +810,6 @@ pub(super) async fn fetch_account_rate_limits(
|
||||
result.wrap_err("account/rateLimits/read failed in TUI")
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_account_token_activity(
|
||||
request_handle: AppServerRequestHandle,
|
||||
) -> Result<codex_app_server_protocol::GetAccountTokenUsageResponse> {
|
||||
let request_id = RequestId::String(format!("account-token-usage-{}", Uuid::new_v4()));
|
||||
request_handle
|
||||
.request_typed(ClientRequest::GetAccountTokenUsage {
|
||||
request_id,
|
||||
params: None,
|
||||
})
|
||||
.await
|
||||
.wrap_err("account/usage/read failed in TUI")
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_thread_usage(
|
||||
request_handle: AppServerRequestHandle,
|
||||
thread_id: ThreadId,
|
||||
|
||||
@@ -1413,9 +1413,6 @@ impl App {
|
||||
self.refresh_rate_limits(app_server, RateLimitRefreshOrigin::Periodic);
|
||||
}
|
||||
}
|
||||
AppEvent::RefreshTokenActivity { request_id } => {
|
||||
self.refresh_token_activity(app_server, request_id);
|
||||
}
|
||||
AppEvent::RefreshThreadUsage {
|
||||
thread_id,
|
||||
request_id,
|
||||
@@ -1605,16 +1602,15 @@ impl App {
|
||||
self.chat_widget.finish_rate_limit_recovery();
|
||||
}
|
||||
},
|
||||
AppEvent::OpenTokenActivity => {
|
||||
self.chat_widget
|
||||
.add_token_activity_output(crate::chatwidget::TokenActivityView::Daily);
|
||||
}
|
||||
AppEvent::OpenAnalytics => {
|
||||
AppEvent::OpenAnalytics { view: summary_view } => {
|
||||
tui.enter_alt_screen()?;
|
||||
let mut view = self.retained_analytics.take().unwrap_or_else(|| {
|
||||
Box::new(crate::analytics::AnalyticsView::new(self.keymap.list.clone()))
|
||||
});
|
||||
view.keymap = self.keymap.list.clone();
|
||||
if summary_view.is_some() {
|
||||
view.select_summary(summary_view);
|
||||
}
|
||||
view.open(
|
||||
app_server.request_handle(),
|
||||
tui.frame_requester(),
|
||||
@@ -1692,22 +1688,6 @@ impl App {
|
||||
);
|
||||
}
|
||||
}
|
||||
AppEvent::TokenActivityLoaded { request_id, result } => {
|
||||
if let Err(err) = &result {
|
||||
tracing::warn!("account/usage/read failed during TUI refresh: {err}");
|
||||
}
|
||||
if self
|
||||
.chat_widget
|
||||
.finish_token_activity_refresh(request_id, result)
|
||||
{
|
||||
// Commit synchronously so an already queued /clear cannot overtake this card.
|
||||
// Do not route through ChatWidget::add_to_history: /usage may complete during
|
||||
// active work, and flushing an in-progress tool cell would corrupt its lifecycle.
|
||||
// If an answer stream is active, keep the settled card transient until its
|
||||
// provisional transcript cells have been consolidated.
|
||||
self.insert_pending_usage_output_if_ready(tui);
|
||||
}
|
||||
}
|
||||
AppEvent::ThreadUsageLoaded {
|
||||
thread_id,
|
||||
request_id,
|
||||
|
||||
@@ -197,9 +197,6 @@ impl App {
|
||||
}
|
||||
|
||||
fn insert_pending_usage_output(&mut self, tui: &mut tui::Tui) {
|
||||
if let Some(cell) = self.chat_widget.take_completed_token_activity_output() {
|
||||
self.insert_history_cell(tui, Box::new(cell));
|
||||
}
|
||||
if let Some(cell) = self.chat_widget.take_pending_rate_limit_reset_hint() {
|
||||
self.insert_history_cell(tui, Box::new(cell));
|
||||
}
|
||||
@@ -324,7 +321,6 @@ impl App {
|
||||
self.deferred_history_lines.clear();
|
||||
self.has_emitted_history_lines = false;
|
||||
self.transcript_reflow.clear();
|
||||
self.chat_widget.clear_pending_token_activity_refreshes();
|
||||
self.chat_widget.clear_pending_rate_limit_reset_hint();
|
||||
self.initial_history_replay_buffer = None;
|
||||
self.scrollback_has_older_history = false;
|
||||
|
||||
@@ -8538,38 +8538,6 @@ async fn refreshed_snapshot_session_persists_resumed_turns() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn late_usage_result_can_follow_finalized_plan() {
|
||||
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
app.chat_widget
|
||||
.add_token_activity_output(crate::chatwidget::TokenActivityView::Daily);
|
||||
let request_id = match app_event_rx.try_recv() {
|
||||
Ok(AppEvent::RefreshTokenActivity { request_id }) => request_id,
|
||||
other => panic!("expected token activity refresh request, got {other:?}"),
|
||||
};
|
||||
|
||||
app.chat_widget.note_stream_consolidation_queued();
|
||||
app.transcript_cells
|
||||
.push(Arc::new(history_cell::new_proposed_plan_stream(
|
||||
vec![Line::from("finalized plan")],
|
||||
/*is_stream_continuation*/ false,
|
||||
)));
|
||||
app.chat_widget.note_stream_consolidation_completed();
|
||||
|
||||
assert!(
|
||||
app.chat_widget.finish_token_activity_refresh(
|
||||
request_id,
|
||||
Err("token activity unavailable".to_string()),
|
||||
)
|
||||
);
|
||||
assert!(!app.pending_usage_output_insertion_blocked());
|
||||
assert!(
|
||||
app.chat_widget
|
||||
.take_completed_token_activity_output()
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn new_session_requests_shutdown_for_previous_conversation() {
|
||||
Box::pin(async {
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
source: tui/src/app/tests/transcript_composer.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Analytics · Live account
|
||||
[7d] 30d · [date range]
|
||||
|
||||
1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills
|
||||
used 6 Top chats
|
||||
|
||||
▎ Plugins called
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
No data reported for this range.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
tab/1–6 section · z dashboard · ←/→ day
|
||||
r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close
|
||||
|
||||
Analytics · Live account
|
||||
[7d] 30d · [date range]
|
||||
|
||||
1 Summary 2 Total usage history 3 Messages [4 Plugins called] 5 Skills
|
||||
used 6 Top chats
|
||||
|
||||
▎ Plugins called
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
No data reported for this range.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
tab/1–6 section · z dashboard · ←/→ day
|
||||
r 7/30d · R refresh · pgup/pgdn scroll · esc back · q close
|
||||
|
||||
Analytics · Live account
|
||||
|
||||
[1 Summary] 2 Total usage history 3 Messages 4 Plugins called 5 Skills
|
||||
used 6 Top chats
|
||||
|
||||
▎ Summary
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
Plus
|
||||
|
||||
12.3K — —
|
||||
Lifetime tokens Peak tokens Longest chat
|
||||
|
||||
— —
|
||||
Current streak Longest streak
|
||||
|
||||
Token activity Daily [Weekly] Cumulative
|
||||
Last 12 months
|
||||
|
||||
Token activity history unavailable
|
||||
|
||||
Activity insights Most used plugins and skills
|
||||
|
||||
tab/1–6 section · z dashboard · ↑/↓ scroll
|
||||
g view · R refresh · pgup/pgdn scroll · esc back · q close
|
||||
@@ -47,8 +47,12 @@ async fn analytics_preserves_the_draft_and_retains_the_view_on_reopen() -> Resul
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
app.chat_widget
|
||||
.apply_external_edit("preserved draft".into());
|
||||
app.handle_event(&mut tui, &mut app_server, AppEvent::OpenAnalytics)
|
||||
.await?;
|
||||
app.handle_event(
|
||||
&mut tui,
|
||||
&mut app_server,
|
||||
AppEvent::OpenAnalytics { view: None },
|
||||
)
|
||||
.await?;
|
||||
for code in [
|
||||
KeyCode::Char('4'),
|
||||
KeyCode::Enter,
|
||||
@@ -62,8 +66,12 @@ async fn analytics_preserves_the_draft_and_retains_the_view_on_reopen() -> Resul
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Char('q')).await?;
|
||||
assert!(app.overlay.is_none());
|
||||
assert!(app.retained_analytics.is_some());
|
||||
app.handle_event(&mut tui, &mut app_server, AppEvent::OpenAnalytics)
|
||||
.await?;
|
||||
app.handle_event(
|
||||
&mut tui,
|
||||
&mut app_server,
|
||||
AppEvent::OpenAnalytics { view: None },
|
||||
)
|
||||
.await?;
|
||||
assert!(matches!(app.overlay, Some(Overlay::Analytics(_))));
|
||||
assert!(app.retained_analytics.is_none());
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Char('q')).await?;
|
||||
@@ -74,6 +82,138 @@ async fn analytics_preserves_the_draft_and_retains_the_view_on_reopen() -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn analytics_menu_reopen_preserves_navigation_and_explicit_view_selects_summary() -> Result<()>
|
||||
{
|
||||
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
let http = wiremock::MockServer::start().await;
|
||||
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
||||
.and(wiremock::matchers::path("/backend-api/wham/accounts/check"))
|
||||
.and(wiremock::matchers::header(
|
||||
"chatgpt-account-id",
|
||||
"test-account",
|
||||
))
|
||||
.respond_with(wiremock::ResponseTemplate::new(/*s*/ 200).set_body_json(
|
||||
serde_json::json!({"accounts": [{"id": "test-account", "plan_type": "plus"}]}),
|
||||
))
|
||||
.expect(/*r*/ 3)
|
||||
.mount(&http)
|
||||
.await;
|
||||
wiremock::Mock::given(wiremock::matchers::method("GET"))
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(/*s*/ 200)
|
||||
.set_body_json(serde_json::json!({"stats":{"lifetime_tokens":12345},"data":[]})),
|
||||
)
|
||||
.mount(&http)
|
||||
.await;
|
||||
app.config.chatgpt_base_url = format!("{}/backend-api", http.uri());
|
||||
app.config.cli_auth_credentials_store_mode = codex_login::AuthCredentialsStoreMode::File;
|
||||
app_test_support::write_chatgpt_auth(
|
||||
&app.config.codex_home,
|
||||
app_test_support::ChatGptAuthFixture::new("test-access-token")
|
||||
.account_id("test-account")
|
||||
.chatgpt_account_id("test-account")
|
||||
.chatgpt_user_id("test-user")
|
||||
.plan_type("plus"),
|
||||
codex_login::AuthCredentialsStoreMode::File,
|
||||
)
|
||||
.unwrap();
|
||||
let mut app_server = start_config_write_test_app_server(&app).await?;
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
let mut screens = Vec::new();
|
||||
for explicit in [
|
||||
None,
|
||||
None,
|
||||
Some(crate::analytics::TokenActivityView::Weekly),
|
||||
] {
|
||||
app.handle_event(
|
||||
&mut tui,
|
||||
&mut app_server,
|
||||
AppEvent::OpenAnalytics { view: explicit },
|
||||
)
|
||||
.await?;
|
||||
tokio::time::timeout(std::time::Duration::from_secs(/*secs*/ 10), async {
|
||||
loop {
|
||||
app.handle_tui_event(&mut tui, &mut app_server, TuiEvent::Draw)
|
||||
.await?;
|
||||
let text = buffer_text(crate::custom_terminal::test_support::last_rendered_buffer(
|
||||
&tui.terminal,
|
||||
));
|
||||
if screens.is_empty() && text.contains("Lifetime tokens") {
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Char('4')).await?;
|
||||
continue;
|
||||
}
|
||||
if text.contains("Lifetime tokens")
|
||||
|| text.contains("No activity")
|
||||
|| text.contains("No data reported")
|
||||
{
|
||||
return Ok::<(), color_eyre::Report>(());
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(/*millis*/ 5)).await;
|
||||
}
|
||||
})
|
||||
.await??;
|
||||
let screen = buffer_text(crate::custom_terminal::test_support::last_rendered_buffer(
|
||||
&tui.terminal,
|
||||
));
|
||||
screens.push(screen);
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Char('q')).await?;
|
||||
}
|
||||
assert_eq!(screens[0], screens[1]);
|
||||
assert!(screens[1].contains("[4 Plugins called]"));
|
||||
assert!(screens[2].contains("[1 Summary]"));
|
||||
assert!(screens[2].contains("[Weekly]"));
|
||||
let snapshot = screens.join("\n\n");
|
||||
let snapshot = snapshot
|
||||
.lines()
|
||||
.map(|line| {
|
||||
if line.starts_with(" [7d] 30d · ") {
|
||||
" [7d] 30d · [date range]"
|
||||
} else {
|
||||
line
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
insta::assert_snapshot!(snapshot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn analytics_keeps_queued_and_late_startup_history_out_of_the_overlay() -> Result<()> {
|
||||
for alternate_screen in [true, false] {
|
||||
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
let mut app_server = start_config_write_test_app_server(&app).await?;
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
tui.set_alt_screen_enabled(alternate_screen);
|
||||
app.insert_history_cell(
|
||||
&mut tui,
|
||||
Box::new(PlainHistoryCell::new(vec!["Startup header".into()])),
|
||||
);
|
||||
assert!(!tui.pending_history_lines_for_test().is_empty());
|
||||
app.handle_event(
|
||||
&mut tui,
|
||||
&mut app_server,
|
||||
AppEvent::OpenAnalytics { view: None },
|
||||
)
|
||||
.await?;
|
||||
assert!(tui.pending_history_lines_for_test().is_empty());
|
||||
app.insert_history_cell(
|
||||
&mut tui,
|
||||
Box::new(PlainHistoryCell::new(vec![
|
||||
"Late startup announcement".into(),
|
||||
])),
|
||||
);
|
||||
assert!(tui.pending_history_lines_for_test().is_empty());
|
||||
let deferred = app.deferred_history_lines.clone();
|
||||
assert!(!deferred.is_empty());
|
||||
press_key(&mut app, &mut tui, &mut app_server, KeyCode::Char('q')).await?;
|
||||
assert_eq!(tui.pending_history_lines_for_test(), deferred);
|
||||
assert_eq!(app.transcript_cells.len(), 2);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn transcript_flag_off_preserves_viewer_and_backtracking() -> Result<()> {
|
||||
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||
|
||||
@@ -19,7 +19,6 @@ use codex_app_server_protocol::AddCreditsNudgeEmailStatus;
|
||||
use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditResponse;
|
||||
use codex_app_server_protocol::DynamicToolCallResponse;
|
||||
use codex_app_server_protocol::GetAccountRateLimitsResponse;
|
||||
use codex_app_server_protocol::GetAccountTokenUsageResponse;
|
||||
use codex_app_server_protocol::MarketplaceAddResponse;
|
||||
use codex_app_server_protocol::MarketplaceRemoveResponse;
|
||||
use codex_app_server_protocol::MarketplaceUpgradeResponse;
|
||||
@@ -678,11 +677,10 @@ pub(crate) enum AppEvent {
|
||||
result: Result<GetAccountRateLimitsResponse, String>,
|
||||
},
|
||||
|
||||
/// Open the default token-activity view selected from the `/usage` menu.
|
||||
OpenTokenActivity,
|
||||
|
||||
/// Open the authenticated account analytics dashboard.
|
||||
OpenAnalytics,
|
||||
OpenAnalytics {
|
||||
view: Option<crate::analytics::TokenActivityView>,
|
||||
},
|
||||
|
||||
/// Open the reset-credit flow selected from the `/usage` menu.
|
||||
OpenRateLimitResetCredits,
|
||||
@@ -711,17 +709,6 @@ pub(crate) enum AppEvent {
|
||||
result: Result<ConsumeAccountRateLimitResetCreditResponse, String>,
|
||||
},
|
||||
|
||||
/// Fetch account-wide token activity for a `/usage` history card.
|
||||
RefreshTokenActivity {
|
||||
request_id: u64,
|
||||
},
|
||||
|
||||
/// Result of fetching account-wide token activity.
|
||||
TokenActivityLoaded {
|
||||
request_id: u64,
|
||||
result: Result<GetAccountTokenUsageResponse, String>,
|
||||
},
|
||||
|
||||
/// Fetch backend-estimated usage for the currently visible enterprise thread.
|
||||
RefreshThreadUsage {
|
||||
thread_id: ThreadId,
|
||||
|
||||
@@ -445,12 +445,11 @@ use self::status_surfaces::CachedProjectRootName;
|
||||
mod thread_title_status;
|
||||
mod thread_usage;
|
||||
pub(crate) use self::thread_usage::ThreadUsageOutcome;
|
||||
mod tokens;
|
||||
pub(crate) use self::tokens::TokenActivityView;
|
||||
mod tool_lifecycle;
|
||||
mod tool_requests;
|
||||
mod transcript;
|
||||
mod transcript_export;
|
||||
mod usage_history;
|
||||
use self::transcript::TranscriptState;
|
||||
mod turn_lifecycle;
|
||||
mod turn_runtime;
|
||||
@@ -610,9 +609,6 @@ pub(crate) struct ChatWidget {
|
||||
rate_limit_snapshots_by_limit_id: BTreeMap<String, RateLimitSnapshotDisplay>,
|
||||
refreshing_status_outputs: Vec<(u64, StatusHistoryHandle)>,
|
||||
next_status_refresh_request_id: u64,
|
||||
refreshing_token_activity_output: Option<tokens::PendingTokenActivityOutput>,
|
||||
completed_token_activity_output: Option<history_cell::CompositeHistoryCell>,
|
||||
next_token_activity_request_id: u64,
|
||||
pending_rate_limit_reset_request_id: Option<u64>,
|
||||
pending_rate_limit_reset_idempotency_key: Option<String>,
|
||||
rate_limit_reset_picker_request_id: Option<u64>,
|
||||
@@ -1985,7 +1981,6 @@ impl ChatWidget {
|
||||
pub(crate) fn active_cell_transcript_key(&self) -> Option<ActiveCellTranscriptKey> {
|
||||
let cell = self.transcript.active_cell.as_ref();
|
||||
let mut realtime_cells = self.realtime_conversation.live_transcript_cells();
|
||||
let token_activity_cell = self.pending_token_activity_output();
|
||||
let rate_limit_reset_hint = self.pending_rate_limit_reset_hint();
|
||||
if cell.is_none()
|
||||
&& self
|
||||
@@ -1994,7 +1989,6 @@ impl ChatWidget {
|
||||
.next()
|
||||
.is_none()
|
||||
&& self.realtime_conversation.pending_history_cells.is_empty()
|
||||
&& token_activity_cell.is_none()
|
||||
&& rate_limit_reset_hint.is_none()
|
||||
{
|
||||
return None;
|
||||
@@ -2036,13 +2030,6 @@ impl ChatWidget {
|
||||
}
|
||||
lines.extend(realtime_lines);
|
||||
}
|
||||
if let Some(token_activity_cell) = self.pending_token_activity_output() {
|
||||
let token_activity_lines = token_activity_cell.transcript_hyperlink_lines(width);
|
||||
if !token_activity_lines.is_empty() && !lines.is_empty() {
|
||||
lines.push(HyperlinkLine::from(""));
|
||||
}
|
||||
lines.extend(token_activity_lines);
|
||||
}
|
||||
if let Some(rate_limit_reset_hint) = self.pending_rate_limit_reset_hint() {
|
||||
let hint_lines = rate_limit_reset_hint.transcript_hyperlink_lines(width);
|
||||
if !hint_lines.is_empty() && !lines.is_empty() {
|
||||
|
||||
@@ -145,9 +145,6 @@ impl ChatWidget {
|
||||
rate_limit_snapshots_by_limit_id: BTreeMap::new(),
|
||||
refreshing_status_outputs: Vec::new(),
|
||||
next_status_refresh_request_id: 0,
|
||||
refreshing_token_activity_output: None,
|
||||
completed_token_activity_output: None,
|
||||
next_token_activity_request_id: 0,
|
||||
pending_rate_limit_reset_request_id: None,
|
||||
pending_rate_limit_reset_idempotency_key: None,
|
||||
rate_limit_reset_picker_request_id: None,
|
||||
|
||||
@@ -174,17 +174,7 @@ impl ChatWidget {
|
||||
})),
|
||||
);
|
||||
}
|
||||
if let Some(cell) = self.pending_token_activity_output() {
|
||||
flex.push(
|
||||
/*flex*/ 1,
|
||||
RenderableItem::Owned(Box::new(TranscriptAreaRenderable {
|
||||
child: cell,
|
||||
top: 1,
|
||||
right: active_cell_right_reserve,
|
||||
persistent_layout: None,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(cell) = self.pending_rate_limit_reset_hint() {
|
||||
flex.push(
|
||||
/*flex*/ 1,
|
||||
|
||||
@@ -213,7 +213,6 @@ impl ChatWidget {
|
||||
self.model_popup_request_id = None;
|
||||
self.invalidate_permission_discovery();
|
||||
self.invalidate_connector_scope();
|
||||
self.clear_pending_token_activity_refreshes();
|
||||
self.clear_pending_rate_limit_reset_requests();
|
||||
self.clear_backend_banner();
|
||||
self.luna_reserve_notice_account_id = None;
|
||||
|
||||
@@ -747,8 +747,10 @@ impl ChatWidget {
|
||||
}
|
||||
SlashCommand::Usage => {
|
||||
if self.ensure_usage_command_available() {
|
||||
match tokens::TokenActivityView::parse(trimmed) {
|
||||
Some(view) => self.add_token_activity_output(view),
|
||||
match crate::analytics::TokenActivityView::parse(trimmed) {
|
||||
Some(view) => self
|
||||
.app_event_tx
|
||||
.send(AppEvent::OpenAnalytics { view: Some(view) }),
|
||||
None => self.add_error_message(
|
||||
"Usage: /usage [daily|weekly|cumulative]".to_string(),
|
||||
),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user