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:
@@ -8,7 +8,7 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件(
|
||||
版式(k=1,点阵字体且**只用整数倍字号 12/24/36/48/84**,不放大;四段,三道横规):
|
||||
A 顶部(0~90) :**大时钟 F84**(墨迹 16~79,占满上下边距内的整段高度)+ 星期/日期 F12 紧随其右、与时钟数字**基线对齐**(左)
|
||||
|天气(右,贴 790)——天气组件与三色版同尺寸。
|
||||
B 用量(96~206) :左半 **Claude Usage**(all 5h / all 7d / fable 7d)|竖规 x=400|右半 **ChatGPT Usage**(mock:all 5h / all 7d / codex 7d)。
|
||||
B 用量(96~206) :左半 **Claude Usage**(两行:5h + 合并行 7d——一个大边框内上下两条实心 = 总 7d / fable 7d,右侧数字取较大者,`_usage_row_split`)|竖规 x=400|右半 **Codex Usage**(5h / 7d,ChatGPT 订阅的 Codex 额度池;未推送时 mock 标「示例数据」)。
|
||||
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31);**三处文字统一 12px**(基类 F13/F16/FPACE 都映射到 12px):
|
||||
标签**常规字重、在标签列内右对齐**(贴进度条),百分比与 pace 数字**粗体**(曾试过 24px,用户嫌大),
|
||||
标签用 bar["label"](statusline 同款写法),列宽按最长标签自适应;标题右侧「上次更新 HH:MM」与今日实时同款。
|
||||
@@ -51,7 +51,7 @@ class E1002Renderer(DashboardRenderer):
|
||||
|
||||
PCT_TRACK = 0 # 百分比是整数倍字号(自带侧边距),不再手动加字符间距
|
||||
LABEL_BOLD = False # 标签常规字重(数值才加粗,层级:数值 > 标签)
|
||||
LABEL_ALIGN = "r" # 标签右对齐贴进度条,长短标签(all 5h / fable 7d)右缘齐平
|
||||
LABEL_ALIGN = "r" # 标签右对齐贴进度条,长短标签(5h / 7d)右缘齐平
|
||||
|
||||
def __init__(self, font_path=config.FONT_PATH):
|
||||
super().__init__(font_path, size=(config.E1002_WIDTH, config.E1002_HEIGHT), k=1)
|
||||
@@ -66,13 +66,29 @@ class E1002Renderer(DashboardRenderer):
|
||||
# ---------- 配色钩子(三档,顺序固定 绿 → 黑 → 红;用量与 pace 各自独立计算)----------
|
||||
_ok, _pace_tol = _cfg.USAGE_OK, _cfg.PACE_TOL # 由 _usage_block 按数据覆盖
|
||||
|
||||
def _usage_row(self, x0, x1, y, bar, warn, label_w=None):
|
||||
"""宽版:pct 为 None(接口未返回该窗口,如 Codex Plus 的 5h)时不画 "--",而是按 0 画——百分比 "00%"、pace "00"、
|
||||
空条、全黑(无数据的中性色,不落绿档)——使各行进度条右端对齐、宽度一致(2026-09-14 用户要求;"--" 会让该行条变短)。"""
|
||||
if not isinstance(bar.get("pct"), (int, float)):
|
||||
bar = dict(bar, pct=0, time_pct=0, _null=True)
|
||||
return super()._usage_row(x0, x1, y, bar, warn, label_w=label_w)
|
||||
|
||||
def _pace(self, xr, cy, diff):
|
||||
"""宽版:diff=0(持平/无数据)不画三角,但**仍预留三角位**(6 + 间隙 4),使每行 pace 列等宽、进度条右端齐平。"""
|
||||
w = super()._pace(xr, cy, diff)
|
||||
return w if diff != 0 else w + 10 * self.k
|
||||
|
||||
def _usage_colors(self, ok, pct, warn, bar):
|
||||
"""进度条填充、边框、百分比数字三者同色:pct ≤ ok 绿「健康」|其间黑「正常」|pct ≥ warn 红「危险」。"""
|
||||
if not ok:
|
||||
"""进度条填充、边框、百分比数字三者同色:pct ≤ ok 绿「健康」|其间黑「正常」|pct ≥ warn 红「危险」;无数据(_null)黑。"""
|
||||
if not ok or bar.get("_null"):
|
||||
return BLACK, BLACK, BLACK
|
||||
c = RED if pct >= warn else GREEN if pct <= self._ok else BLACK
|
||||
return c, c, c
|
||||
|
||||
def _cmp_color(self, direction, fg=BLACK):
|
||||
"""环比/同比 涨绿 / 跌红(与底部 K 线「涨绿跌红」一致,2026-09-14 用户要求);无方向沿用 fg。"""
|
||||
return GREEN if direction == "up" else RED if direction == "down" else fg
|
||||
|
||||
def _pace_color(self, diff):
|
||||
"""pace = 用量% − 时间%(与用量档位独立):≤ −tol 绿「节余」|(−tol, +tol) 黑「持平」|≥ +tol 红「偏快」。"""
|
||||
if diff >= self._pace_tol:
|
||||
@@ -101,14 +117,48 @@ class E1002Renderer(DashboardRenderer):
|
||||
self._label(y0, usage.get("title", "Usage"), right=right, x0=x0, x1=x1)
|
||||
warn = usage.get("warn", 80)
|
||||
self._ok, self._pace_tol = usage.get("ok", _cfg.USAGE_OK), usage.get("pace_tol", _cfg.PACE_TOL)
|
||||
bars = [dict(b, k=b.get("label") or b["k"]) for b in usage.get("bars", [])] # 宽版用长标签(all 5h / fable 7d)
|
||||
label_w = max([self._tw(b["k"], self.F13) for b in bars] + [26]) + self.LABEL_GAP
|
||||
y = y0 + 27
|
||||
bars = [dict(b, k=b.get("label") or b["k"]) for b in usage.get("bars", [])] # 宽版用长标签(5h / 7d)
|
||||
# scoped 行(fable 7d)并入紧邻其前的非 scoped 行,成为该行的下半细条 → 左右两块都是两行,对称
|
||||
rows = []
|
||||
for b in bars:
|
||||
self._usage_row(x0, x1, y, b, warn, label_w=label_w)
|
||||
if b.get("scoped") and rows and rows[-1][1] is None:
|
||||
rows[-1] = (rows[-1][0], b)
|
||||
else:
|
||||
rows.append((b, None))
|
||||
label_w = max([self._tw(m["k"], self.F13) for m, _ in rows] + [26]) + self.LABEL_GAP
|
||||
y = y0 + 27
|
||||
for m, sub in rows:
|
||||
if sub is None:
|
||||
self._usage_row(x0, x1, y, m, warn, label_w=label_w)
|
||||
else:
|
||||
self._usage_row_split(x0, x1, y, m, sub, warn, label_w)
|
||||
y += 31
|
||||
return y - 31 + 18 # 末行进度条底部(cy=y+10,条高 15 → 底 y+17)
|
||||
|
||||
def _usage_row_split(self, x0, x1, y, main, sub, warn, label_w):
|
||||
"""合并行「7d」(2026-09-14 定稿):**一个大边框**、整行高度与单行相同(H=15);边框内部上下**精确对半**两条**实心**填充
|
||||
(各 7px、紧挨无空隙)——上 = main(总 7d),下 = sub(模型限定 fable 7d)。右侧百分比与 pace **只显示两者中较大者**
|
||||
(同一窗口 time_pct 相同,pct 大者 pace 也大);边框与文字颜色随该较大者的档位,两条填充各按自身档位取色。
|
||||
实现:以「较大者」合成一条 bar 交给基类 _usage_row 画边框/标签/文字,再把边框内部刷白、重绘两条半高填充。"""
|
||||
k = self.k
|
||||
def _p(b):
|
||||
v = (b or {}).get("pct")
|
||||
return int(v) if isinstance(v, (int, float)) else None
|
||||
pm, ps = _p(main), _p(sub)
|
||||
dom = sub if (ps is not None and (pm is None or ps > pm)) else main
|
||||
bx0, bx1, by, H = self._usage_row(x0, x1, y, dict(dom, k=main["k"]), warn, label_w=label_w)
|
||||
# 边框内部刷白(保留 1px 边框),再画上下两条实心
|
||||
self.d.rectangle([bx0 + k, by + k, bx1 - k, by + H - k], fill=WHITE)
|
||||
halves = ((pm, main, by + k, by + 7 * k), (ps, sub, by + 8 * k, by + 14 * k)) # 边框内 14px 精确对半:上 by+1..by+7,下 by+8..by+14,无空隙
|
||||
for pct, b, top, bot in halves:
|
||||
if pct is None:
|
||||
continue
|
||||
pct = max(0, min(100, pct))
|
||||
fill = self._usage_colors(True, pct, warn, b or {})[0]
|
||||
fw = (bx1 - bx0) * pct / 100.0
|
||||
if fw >= 2:
|
||||
self.d.rectangle([bx0 + k, top, min(bx0 + fw, bx1 - k), bot], fill=fill)
|
||||
|
||||
def _realtime(self, y0, mao, x0=8, x1=None):
|
||||
"""橘喵·今日实时五列(宽版):探店单量 / 探店流水 / 团购单量 / 团购流水 / 毛利,列内四行逐行水平居中:
|
||||
指标名 F12(y0+21)→ 值 F24 粗(y0+38)→ 环比(y0+72)→ 同比(y0+88,行距 16);按墨迹框算,标题↔名 ~9px(原 ~5px 太挤,整列下移 4px)、
|
||||
@@ -229,7 +279,7 @@ class E1002Renderer(DashboardRenderer):
|
||||
self._weather_category(wt.get("icon")), s=s, accent=RED, wet=BLUE)
|
||||
self.hdash(8, self.W - 8, DIV_Y)
|
||||
|
||||
# ---- B. 用量:左 Claude | 右 ChatGPT(紧凑单行,与三色版同款)----
|
||||
# ---- B. 用量:左 Claude | 右 Codex(紧凑单行,与三色版同款)----
|
||||
MID = self.W // 2 # 400
|
||||
y_l = self._usage_block(10, MID - 14, DIV_Y + 6, usage)
|
||||
y_r = self._usage_block(MID + 14, R, DIV_Y + 6, gpt) if gpt else y_l
|
||||
|
||||
Reference in New Issue
Block a user