feat: AI 用量改账户级本机采集(Claude 钥匙串/Codex RPC)+宽版 7d 合并行
- Claude:复用 Claude Code 钥匙串登录态直调 /api/oauth/usage,零依赖 statusline 与 Claude Code 进程;只读不刷新 token - Codex:经 codex app-server JSON-RPC 读 ChatGPT 订阅的 Codex 额度池(普通聊天额度无账户级途径,右块改名 Codex Usage) - 两路采集共用 TTL 缓存+失败退避(CLAUDE_USAGE_TTL/CODEX_USAGE_TTL,语义同 WEATHER_TTL),推送端两路独立上报 - E1002:Claude 7d 与 fable 7d 合并为一行(单边框内上下对半实心,数字取较大者)、标签去 all 前缀、空值按 00% 00 占位等宽 - E1002/网页版:今日实时环比同比改涨绿跌红,与 K 线一致 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
53
data.py
53
data.py
@@ -18,7 +18,8 @@ from zoneinfo import ZoneInfo
|
||||
import config
|
||||
import jm_api
|
||||
import weather_api
|
||||
import usage_local
|
||||
import claude_usage
|
||||
import codex_usage
|
||||
import btc_api
|
||||
|
||||
WEEK_CN = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
@@ -311,26 +312,28 @@ def _fmt_reset(resets_at, window):
|
||||
|
||||
|
||||
# Claude 用量窗口:(快照键,短标签,长标签,窗口长度秒,scoped)。
|
||||
# 短标签 k 给 400x300 小屏(标签列只有 26px);长标签 label 给 800x480 宽版,沿用 statusline 的写法
|
||||
# (all 5h / all 7d / fable 7d,小写开头)。scoped=True 为「按模型限定」的额度(Fable 7d,来自
|
||||
# /api/oauth/usage 的 limits[],由 statusline 写入快照 seven_day_fable),小屏放不下第三行,只在宽版展示。
|
||||
# 短标签 k 给 400x300 小屏(标签列只有 26px);长标签 label 给 800x480 宽版(2026-09-14 去掉 all 前缀,就叫 5h / 7d)。
|
||||
# scoped=True 为「按模型限定」的额度(Fable 7d,来自 /api/oauth/usage 的 limits[] weekly_scoped,由 claude_usage.normalize
|
||||
# 写入快照 seven_day_fable):小屏 render() 跳过;宽版**并入紧邻其前的非 scoped 行**(7d 行拆成上下两条细条:上 = 总 7d,
|
||||
# 下 = fable 7d),使左右两块都是两行、视觉对称(此前三行 vs 两行不对称)。
|
||||
USAGE_WINDOWS = (
|
||||
("five_hour", "5h", "all 5h", FIVE_HOUR, False),
|
||||
("seven_day", "7d", "all 7d", SEVEN_DAY, False),
|
||||
("five_hour", "5h", "5h", FIVE_HOUR, False),
|
||||
("seven_day", "7d", "7d", SEVEN_DAY, False),
|
||||
("seven_day_fable", "Fable 7d", "fable 7d", SEVEN_DAY, True),
|
||||
)
|
||||
# ChatGPT 用量窗口:键名沿用同一套约定(five_hour / seven_day / seven_day_codex),推送端照此结构提供原始数据即可。
|
||||
# Codex 用量窗口(ChatGPT 订阅的 Codex 额度池,由 codex_usage.py 经 app-server RPC 取得):只有 5h / 7d 两个窗口。
|
||||
# ChatGPT 普通聊天额度没有账户级读取途径(2026-09 调研结论),故右侧块监控的就是 Codex 池并如实命名「Codex Usage」。
|
||||
# 快照里各窗口可带 window(真实窗口秒数),pace 优先按它算;rateLimitsByLimitId 多桶原样存于 buckets,看到真实数据后再决定是否加第三行。
|
||||
GPT_WINDOWS = (
|
||||
("five_hour", "5h", "all 5h", FIVE_HOUR, False),
|
||||
("seven_day", "7d", "all 7d", SEVEN_DAY, False),
|
||||
("seven_day_codex", "Codex 7d", "codex 7d", SEVEN_DAY, True),
|
||||
("five_hour", "5h", "5h", FIVE_HOUR, False),
|
||||
("seven_day", "7d", "7d", SEVEN_DAY, False),
|
||||
)
|
||||
|
||||
|
||||
def usage_from_raw(now, raw, windows, title):
|
||||
"""原始快照 → 渲染结构(Claude / ChatGPT 通用)。
|
||||
raw 形如 {five_hour:{utilization, resets_at}, seven_day:{...}, <scoped 键>:{...}|null, updated_at?};
|
||||
utilization 为已用百分比,resets_at 为 epoch 秒或 ISO;缺哪个窗口哪个显示 "--"。
|
||||
raw 形如 {five_hour:{utilization, resets_at, window?}, seven_day:{...}, <scoped 键>:{...}|null, updated_at?};
|
||||
utilization 为已用百分比,resets_at 为 epoch 秒或 ISO,window 为该窗口真实长度秒(缺省用 windows 表的常量);缺哪个窗口哪个显示 "--"。
|
||||
time_pct(时间已流逝%)与 pace 按传入的 now **现算**——所以推送端只需推原始快照,服务端渲染时 pace 仍随时钟准确推进。
|
||||
结构契约(renderer 依赖):{title, warn, ok, pace_tol, updated?, bars:[{k, label, pct(0~100 或 None), time_pct?, reset?, scoped}]};
|
||||
pct ≥ warn 时该条进度条与百分比转红(宽版另按 ok/warn 分 绿/黑/红 三档,百分比随条色);
|
||||
@@ -343,6 +346,7 @@ def usage_from_raw(now, raw, windows, title):
|
||||
util = seg.get("utilization")
|
||||
pct = int(round(float(util))) if util is not None else None
|
||||
bar = {"k": k, "label": label, "pct": pct, "scoped": scoped}
|
||||
win = int(seg.get("window") or win)
|
||||
tp = _window_time_pct(seg.get("resets_at"), win, now)
|
||||
if pct is not None and tp is not None:
|
||||
bar["time_pct"] = tp
|
||||
@@ -358,31 +362,34 @@ def usage_from_raw(now, raw, windows, title):
|
||||
|
||||
|
||||
def _usage(now, raw=None):
|
||||
"""Claude Code 用量(5h / 7d / Fable 7d)。raw 为 None 时读本地 statusline 快照(见 usage_local.py,**不发请求**);
|
||||
服务端传入推送来的原始快照。utilization 来自 statusline 从 Claude Code stdin 落盘的权威实时值(Fable 行来自其转存的 API limits)。"""
|
||||
"""Claude 账户级用量(5h / 7d / Fable 7d)。raw 为 None 时本机经 claude_usage 采集(复用 Claude Code 钥匙串登录态调
|
||||
/api/oauth/usage,带 ≥300s 最小间隔缓存与退避);服务端传入推送来的原始快照。"""
|
||||
if raw is None:
|
||||
raw = {}
|
||||
try:
|
||||
raw = usage_local.get_usage()
|
||||
raw = claude_usage.get_usage()
|
||||
except Exception as e:
|
||||
print(f"[警告] Claude 用量读取失败:{e}")
|
||||
return usage_from_raw(now, raw, USAGE_WINDOWS, "Claude Usage")
|
||||
|
||||
|
||||
def gpt_mock_raw(now):
|
||||
"""ChatGPT 原始快照的 mock(尚无真实数据源时用):刻意覆盖宽版三档配色——
|
||||
5h 88%(≥warn 红)、7d 71%(黑)、Codex 7d 22%(≤ok 绿,scoped,与左侧 fable 行对称)。"""
|
||||
"""Codex 原始快照的 mock(服务端尚未收到推送时用):5h 88%(≥warn 红)、7d 22%(≤ok 绿)覆盖两端配色。"""
|
||||
t = now.timestamp()
|
||||
return {"five_hour": {"utilization": 88, "resets_at": t + 2 * 3600},
|
||||
"seven_day": {"utilization": 71, "resets_at": t + 3 * 86400},
|
||||
"seven_day_codex": {"utilization": 22, "resets_at": t + 3 * 86400}}
|
||||
"seven_day": {"utilization": 22, "resets_at": t + 3 * 86400}}
|
||||
|
||||
|
||||
def _usage_gpt(now, raw=None):
|
||||
"""ChatGPT 用量。raw 为推送来的原始快照(结构同 gpt_mock_raw);为 None 时用 mock 并标 mock=True
|
||||
(renderer 在标题右侧标「示例数据」)。接入真实数据源时由推送端提供 raw,本函数无需改动。"""
|
||||
def _usage_gpt(now, raw=None, allow_local=True):
|
||||
"""Codex 用量(ChatGPT 订阅的 Codex 额度池)。raw 为推送来的原始快照(codex_usage 产出,结构同 gpt_mock_raw);
|
||||
服务端未收到推送时(raw=None)用 mock 并标 mock=True(renderer 在标题右侧标「示例数据」)。本机形态直接经 codex_usage 采集。"""
|
||||
mock = raw is None
|
||||
out = usage_from_raw(now, gpt_mock_raw(now) if mock else raw, GPT_WINDOWS, "ChatGPT Usage")
|
||||
if mock and allow_local:
|
||||
try:
|
||||
raw, mock = codex_usage.get_usage(), False
|
||||
except Exception as e:
|
||||
print(f"[警告] Codex 用量读取失败:{e}")
|
||||
out = usage_from_raw(now, gpt_mock_raw(now) if mock else raw, GPT_WINDOWS, "Codex Usage")
|
||||
if mock:
|
||||
out["mock"] = True
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user