diff --git a/usage-dashboard/usage-dashboard.py b/usage-dashboard/usage-dashboard.py index 858d696..aea1339 100644 --- a/usage-dashboard/usage-dashboard.py +++ b/usage-dashboard/usage-dashboard.py @@ -414,24 +414,72 @@ def refresh_quota(force=False): print(f"refresh_quota: saved quota for {email} (plan: {data.get('plan_type')})", flush=True) elif basename.startswith("xai"): - # xAI endpoint - req = urllib.request.Request( - "https://api.x.ai/v1/api-key", - headers={ - "Authorization": "Bearer " + token, - "Accept": "application/json", - "User-Agent": "codex-cli", - }, - ) - with urllib.request.urlopen(req, timeout=20) as resp: - data = json.load(resp) + # xAI quota endpoint - thử các endpoint phổ biến + # Dựa trên tài liệu xAI và các project liên quan, quota có thể ở /v1/usage hoặc /v1/rate-limits + xai_endpoints = [ + "https://api.x.ai/v1/usage", + "https://api.x.ai/v1/rate-limits", + "https://api.x.ai/v1/api-key", # fallback + ] + data = None + used_endpoint = None - # xAI trả về format khác, cần parse theo cấu trúc của nó - # Giả sử xAI trả về: {"rate_limit": {"remaining": 100, "limit": 1000, "reset_at": ...}} - rl = data.get("rate_limit") or {} - remaining = int(rl.get("remaining") or 0) - limit = int(rl.get("limit") or 100) - used_percent = int((limit - remaining) * 100 / limit) if limit > 0 else 0 + for endpoint in xai_endpoints: + try: + req = urllib.request.Request( + endpoint, + headers={ + "Authorization": "Bearer " + token, + "Accept": "application/json", + "User-Agent": "codex-cli", + }, + ) + with urllib.request.urlopen(req, timeout=20) as resp: + data = json.load(resp) + used_endpoint = endpoint + print(f"refresh_quota: xAI quota fetched from {endpoint}", flush=True) + break + except urllib.error.HTTPError as e: + if e.code == 404: + continue # Thử endpoint tiếp theo + raise + except Exception: + continue + + if data is None: + print(f"refresh_quota: xAI quota - no valid endpoint responded for {email}", flush=True) + continue + + # Parse xAI response - hỗ trợ nhiều format có thể có + # Format 1: {"usage": {...}, "limits": {...}} + # Format 2: {"rate_limit": {...}} + # Format 3: {"quota": {...}} + rl = data.get("rate_limit") or data.get("limits") or data.get("quota") or {} + + # Tính toán used_percent từ các trường có thể có + used_percent = 0 + reset_at = None + limit_reached = False + + if "used_percent" in rl: + used_percent = int(rl.get("used_percent") or 0) + reset_at = rl.get("reset_at") + limit_reached = bool(rl.get("limit_reached")) + elif "remaining" in rl and "limit" in rl: + remaining = int(rl.get("remaining") or 0) + limit = int(rl.get("limit") or 100) + used_percent = int((limit - remaining) * 100 / limit) if limit > 0 else 0 + reset_at = rl.get("reset_at") + limit_reached = remaining <= 0 + elif "used" in rl and "total" in rl: + used = int(rl.get("used") or 0) + total = int(rl.get("total") or 100) + used_percent = int(used * 100 / total) if total > 0 else 0 + reset_at = rl.get("reset_at") + limit_reached = used >= total + + plan_type = data.get("plan_type") or data.get("plan") or "xai" + credits_balance = str(data.get("credits_balance") or data.get("balance") or "") conn.execute( """ @@ -446,21 +494,21 @@ def refresh_quota(force=False): now.isoformat(), now.timestamp(), email, - data.get("plan_type", "xai"), - 1 if not rl.get("limit_reached") else 0, - 1 if rl.get("limit_reached") else 0, + plan_type, + 1 if not limit_reached else 0, + 1 if limit_reached else 0, used_percent, max(0, 100 - used_percent), - epoch_to_local(rl.get("reset_at")), + epoch_to_local(reset_at), 0, # xAI không có secondary window 0, "", - str(data.get("credits_balance", "")), + credits_balance, json.dumps(data, ensure_ascii=False), ), ) inserted += 1 - print(f"refresh_quota: saved quota for {email} (provider: xAI)", flush=True) + print(f"refresh_quota: saved quota for {email} (provider: xAI, plan: {plan_type}, used: {used_percent}%)", flush=True) else: print(f"refresh_quota: skipping {email} (unknown prefix: {basename})", flush=True)