v2 Phase 4: add "generated" (output) tokens under the total on Session

The total is ~97% cache reads, which reads as implausibly large on its own.
Show the cache-inclusive total as the headline token number and the output
tokens ("what Claude actually generated", ~1.1M) as a small line beneath it —
impressive total, believable sub-number. Daemon sends a new `to` field; firmware
parses output_today and renders it. Re-spaced the Session layout for the 5th row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wenil
2026-06-20 15:14:27 +03:00
co-authored by Claude Opus 4.8
parent 466e32fda1
commit c2020dcea2
4 changed files with 20 additions and 6 deletions
+4 -1
View File
@@ -90,6 +90,7 @@ def compute_today_usage() -> dict:
today = datetime.date.today() today = datetime.date.today()
midnight = datetime.datetime.combine(today, datetime.time.min) midnight = datetime.datetime.combine(today, datetime.time.min)
total_tokens = 0 total_tokens = 0
output_tokens = 0
cost = 0.0 cost = 0.0
messages = 0 messages = 0
try: try:
@@ -133,13 +134,15 @@ def compute_today_usage() -> dict:
cw = usage.get("cache_creation_input_tokens", 0) or 0 cw = usage.get("cache_creation_input_tokens", 0) or 0
cr = usage.get("cache_read_input_tokens", 0) or 0 cr = usage.get("cache_read_input_tokens", 0) or 0
total_tokens += inp + out + cw + cr total_tokens += inp + out + cw + cr
output_tokens += out
messages += 1 messages += 1
p = _price_for(msg.get("model")) p = _price_for(msg.get("model"))
cost += (inp * p["in"] + out * p["out"] cost += (inp * p["in"] + out * p["out"]
+ cw * p["cache_w"] + cr * p["cache_r"]) / 1_000_000 + cw * p["cache_w"] + cr * p["cache_r"]) / 1_000_000
except OSError: except OSError:
continue continue
return {"tk": int(total_tokens), "tc": int(round(cost * 100)), "tn": messages} return {"tk": int(total_tokens), "tc": int(round(cost * 100)),
"tn": messages, "to": int(output_tokens)}
def _build_file_logger() -> logging.Logger | None: def _build_file_logger() -> logging.Logger | None:
+2 -1
View File
@@ -13,6 +13,7 @@ struct UsageData {
// Today's Claude Code usage (Phase 4 — computed by the daemon from local // Today's Claude Code usage (Phase 4 — computed by the daemon from local
// transcripts). Tokens can exceed 2^31 on a heavy day, so use 64-bit. // transcripts). Tokens can exceed 2^31 on a heavy day, so use 64-bit.
long long tokens_today; // total tokens today (input+output+cache) long long tokens_today; // total tokens today (input+output+cache)
long long output_today; // output tokens today (what Claude generated)
int cost_cents_today; // equivalent API cost today, US cents int cost_cents_today; // equivalent API cost today, US cents
int messages_today; // assistant messages today int messages_today; // assistant requests (API turns) today
}; };
+1
View File
@@ -113,6 +113,7 @@ static bool parse_json(const char* json, UsageData* out) {
strlcpy(out->status, doc["st"] | "unknown", sizeof(out->status)); strlcpy(out->status, doc["st"] | "unknown", sizeof(out->status));
out->ok = doc["ok"] | false; out->ok = doc["ok"] | false;
out->tokens_today = doc["tk"] | (long long)0; out->tokens_today = doc["tk"] | (long long)0;
out->output_today = doc["to"] | (long long)0;
out->cost_cents_today = doc["tc"] | 0; out->cost_cents_today = doc["tc"] | 0;
out->messages_today = doc["tn"] | 0; out->messages_today = doc["tn"] | 0;
out->valid = true; out->valid = true;
+13 -4
View File
@@ -132,6 +132,7 @@ static lv_obj_t* bt_mac_lbl;
static lv_obj_t* session_container; // today's tokens / cost static lv_obj_t* session_container; // today's tokens / cost
static lv_obj_t* sess_cost_lbl; static lv_obj_t* sess_cost_lbl;
static lv_obj_t* sess_tokens_lbl; static lv_obj_t* sess_tokens_lbl;
static lv_obj_t* sess_gen_lbl;
static lv_obj_t* sess_msgs_lbl; static lv_obj_t* sess_msgs_lbl;
// App registry — the launcher renders one tile per entry, so adding a screen is // App registry — the launcher renders one tile per entry, so adding a screen is
@@ -704,25 +705,31 @@ static void init_session_screen(lv_obj_t* scr) {
lv_label_set_text(sess_cost_lbl, "$0.00"); lv_label_set_text(sess_cost_lbl, "$0.00");
lv_obj_set_style_text_font(sess_cost_lbl, &font_tiempos_56, 0); lv_obj_set_style_text_font(sess_cost_lbl, &font_tiempos_56, 0);
lv_obj_set_style_text_color(sess_cost_lbl, COL_TEXT, 0); lv_obj_set_style_text_color(sess_cost_lbl, COL_TEXT, 0);
lv_obj_align(sess_cost_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 30); lv_obj_align(sess_cost_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 25);
lv_obj_t* sub = lv_label_create(session_container); lv_obj_t* sub = lv_label_create(session_container);
lv_label_set_text(sub, "spent today"); lv_label_set_text(sub, "spent today");
lv_obj_set_style_text_font(sub, &font_styrene_20, 0); lv_obj_set_style_text_font(sub, &font_styrene_20, 0);
lv_obj_set_style_text_color(sub, COL_DIM, 0); lv_obj_set_style_text_color(sub, COL_DIM, 0);
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, L.content_y + 110); lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, L.content_y + 105);
sess_tokens_lbl = lv_label_create(session_container); sess_tokens_lbl = lv_label_create(session_container);
lv_label_set_text(sess_tokens_lbl, "\xE2\x80\x94 tokens"); lv_label_set_text(sess_tokens_lbl, "\xE2\x80\x94 tokens");
lv_obj_set_style_text_font(sess_tokens_lbl, &font_styrene_28, 0); lv_obj_set_style_text_font(sess_tokens_lbl, &font_styrene_28, 0);
lv_obj_set_style_text_color(sess_tokens_lbl, COL_ACCENT, 0); lv_obj_set_style_text_color(sess_tokens_lbl, COL_ACCENT, 0);
lv_obj_align(sess_tokens_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 165); lv_obj_align(sess_tokens_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 152);
sess_gen_lbl = lv_label_create(session_container);
lv_label_set_text(sess_gen_lbl, "\xE2\x80\x94 generated");
lv_obj_set_style_text_font(sess_gen_lbl, &font_styrene_16, 0);
lv_obj_set_style_text_color(sess_gen_lbl, COL_DIM, 0);
lv_obj_align(sess_gen_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 192);
sess_msgs_lbl = lv_label_create(session_container); sess_msgs_lbl = lv_label_create(session_container);
lv_label_set_text(sess_msgs_lbl, "\xE2\x80\x94 requests"); lv_label_set_text(sess_msgs_lbl, "\xE2\x80\x94 requests");
lv_obj_set_style_text_font(sess_msgs_lbl, &font_styrene_20, 0); lv_obj_set_style_text_font(sess_msgs_lbl, &font_styrene_20, 0);
lv_obj_set_style_text_color(sess_msgs_lbl, COL_DIM, 0); lv_obj_set_style_text_color(sess_msgs_lbl, COL_DIM, 0);
lv_obj_align(sess_msgs_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 215); lv_obj_align(sess_msgs_lbl, LV_ALIGN_TOP_MID, 0, L.content_y + 232);
lv_obj_add_flag(session_container, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(session_container, LV_OBJ_FLAG_HIDDEN);
} }
@@ -805,6 +812,8 @@ void ui_update(const UsageData* data) {
data->cost_cents_today / 100, data->cost_cents_today % 100); data->cost_cents_today / 100, data->cost_cents_today % 100);
format_tokens(data->tokens_today, buf, sizeof(buf)); format_tokens(data->tokens_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_tokens_lbl, "%s tokens", buf); lv_label_set_text_fmt(sess_tokens_lbl, "%s tokens", buf);
format_tokens(data->output_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_gen_lbl, "%s generated", buf);
lv_label_set_text_fmt(sess_msgs_lbl, "%d requests", data->messages_today); lv_label_set_text_fmt(sess_msgs_lbl, "%d requests", data->messages_today);
} }
} }