Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/panels/classic.html
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@
margin-left: auto;
}

.card[data-card="codex"] .brand.usage-switch-host h1 {
flex: none;
overflow: visible;
}

.card[data-card="codex"] .brand.usage-switch-host .codex-stale {
flex: 1 1 0;
overflow: hidden;
}

.card[data-card="codex"] .brand.usage-switch-host [data-codex-stale-age] {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}

.codex-stale-info {
position: relative;
display: inline-flex;
Expand Down
59 changes: 55 additions & 4 deletions assets/panels/panel_core.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@
const I18N = {{I18N_BUNDLE}};
const FALLBACK_LANGUAGE = "en";
const root = document.documentElement;
const switchHostStyle = document.createElement("style");
switchHostStyle.textContent = `
[data-card="codex"] .usage-switch-host > h1,
[data-card="codex"] .usage-switch-host > .header-copy {
flex: 0 0 auto !important;
min-width: max-content !important;
overflow: visible !important;
}
[data-card="codex"] .usage-switch-host > [data-codex-stale] {
flex: 1 1 0 !important;
min-width: 0 !important;
overflow: hidden !important;
}
[data-card="codex"] .usage-switch-host [data-codex-stale-age] {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
`;
document.head.appendChild(switchHostStyle);
let currentLanguage = "en";
let projectRange = "1d";
let latestState = null;
Expand All @@ -87,6 +108,7 @@
}

function projectRangeLabel(range) {
if (range === "yesterday") return t("project_range_yesterday");
if (range === "7d") return t("project_range_7d");
if (range === "30d") return t("project_range_30d");
if (range === "all") return t("project_range_all");
Expand All @@ -99,6 +121,13 @@
const key = node.dataset.i18n;
if (key) node.textContent = t(key);
});
document.querySelectorAll("[data-i18n-title]").forEach((node) => {
const key = node.dataset.i18nTitle;
if (!key) return;
const text = t(key);
node.title = text;
node.setAttribute("aria-label", text);
});
const rangeButton = document.querySelector('[data-action="toggle-project-range"]');
if (rangeButton) rangeButton.textContent = projectRangeLabel(projectRange);
}
Expand Down Expand Up @@ -262,6 +291,14 @@
});
}

function renderPeriodTotal(state) {
const total = document.querySelector('[data-footer="today"]');
if (!total) return;
total.textContent = projectRange === "yesterday"
? (state.footer.yesterday || t("yesterday_text", { cost: "0.00", tokens: "0" }))
: (state.footer.today || t("today_text", { cost: "0.00", tokens: "0" }));
}




Expand All @@ -277,7 +314,15 @@
}

function relocateSwitchButton(state) {
document.querySelectorAll(".usage-switch-host").forEach((host) => {
host.classList.remove("usage-switch-host");
});
window.PanelHooks.switchButtonStrategy(state);
const button = document.querySelector('[data-action="switch"]');
const host = button && button.parentElement;
if (host && host.closest('[data-card="codex"]')) {
host.classList.add("usage-switch-host");
}
}

const QUOTA_CARD_IDS = ["claude", "codex", "agy"];
Expand Down Expand Up @@ -315,6 +360,7 @@
latestState = state;
renderProjects(
projectRange === "1d" ? state.projects
: projectRange === "yesterday" ? state.projectsYesterday
: projectRange === "7d" ? state.projects7d
: projectRange === "30d" ? state.projects30d
: projectRange === "all" ? state.projectsAll
Expand All @@ -323,12 +369,11 @@
renderStatusline(state.statusline || {});
const rate = document.querySelector('[data-footer="rate"]');
const status = document.querySelector('[data-footer="status"]');
const today = document.querySelector('[data-footer="today"]');
const serviceAlerts = document.querySelector('[data-footer="service-alerts"]');
const install = document.querySelector('[data-action="install"]');
if (rate) rate.textContent = state.footer.rate || t("rate_text", { value: "--" });
if (status) status.textContent = state.footer.status || t("status_text", { value: "--" });
if (today) today.textContent = state.footer.today || t("today_text", { cost: "0.00", tokens: "0" });
renderPeriodTotal(state);
if (install) install.dataset.visible = state.footer.showInstall === true ? "true" : "false";
if (serviceAlerts) {
const alerts = state.footer.serviceAlerts || [];
Expand All @@ -346,16 +391,21 @@
const button = event.target.closest("[data-action]");
if (!button) return;
if (button.dataset.action === "toggle-project-range") {
projectRange = projectRange === "1d" ? "7d" : projectRange === "7d" ? "30d" : projectRange === "30d" ? "all" : "1d";
projectRange = projectRange === "1d" ? "yesterday" : projectRange === "yesterday" ? "7d" : projectRange === "7d" ? "30d" : projectRange === "30d" ? "all" : "1d";
button.textContent = projectRangeLabel(projectRange);
if (latestState) {
renderProjects(
projectRange === "1d" ? latestState.projects
: projectRange === "yesterday" ? latestState.projectsYesterday
: projectRange === "7d" ? latestState.projects7d
: projectRange === "30d" ? latestState.projects30d
: projectRange === "all" ? latestState.projectsAll
: latestState.projects
);
renderPeriodTotal(latestState);
if (typeof window.usageRequestContentHeight === "function") {
window.usageRequestContentHeight();
}
}
return;
}
Expand Down Expand Up @@ -421,9 +471,10 @@
cardOrder: ["claude", "codex", "agy"],
hideAgy: true,
projects: [],
projectsYesterday: [],
projects7d: [],
projects30d: [],
projectsAll: [],
statusline: {},
footer: { rate: "Rate: --", status: "Status: Loading", today: "Today: $0.00 (0 tokens)", serviceAlerts: [], showInstall: false }
footer: { rate: "Rate: --", status: "Status: Loading", today: "Today: $0.00 (0 tokens)", yesterday: "Yesterday: $0.00 (0 tokens)", serviceAlerts: [], showInstall: false }
});
Binary file added assets/usage.ico
Binary file not shown.
15 changes: 13 additions & 2 deletions codex_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class CodexRateLimits:
has_credits: bool = False
credit_balance: str | None = None
credits_unlimited: bool = False
limit_id: str = ""


def _seed_caches_from_disk() -> None:
Expand Down Expand Up @@ -416,11 +417,16 @@ def _load_jsonl_rate_limits(
return None
models = _load_thread_models()
# scan 30 recent sessions because short/interrupted Codex sessions write null rate_limits
fallback: CodexRateLimits | None = None
for path in _recent_jsonl_files(jsonl_candidates=jsonl_candidates):
rate_limits = _extract_rate_limits(path, models)
if rate_limits is not None:
if rate_limits is None:
continue
if rate_limits.limit_id == "codex":
return rate_limits
return None
if fallback is None:
fallback = rate_limits
return fallback


def _rate_limits_timestamp(rate_limits: CodexRateLimits) -> datetime:
Expand Down Expand Up @@ -930,6 +936,7 @@ def _extract_rate_limits(path: Path, models: dict[str, str]) -> CodexRateLimits
session_id = ""
session_model = "unknown"
last_rate_limits: tuple[dict[str, Any], str] | None = None
last_general_rate_limits: tuple[dict[str, Any], str] | None = None
try:
with path.open(encoding="utf-8") as file:
for line in file:
Expand All @@ -951,9 +958,12 @@ def _extract_rate_limits(path: Path, models: dict[str, str]) -> CodexRateLimits
rate_limits = _as_dict(payload.get("rate_limits"))
if rate_limits:
last_rate_limits = (rate_limits, _as_str(data.get("timestamp")))
if rate_limits.get("limit_id") == "codex":
last_general_rate_limits = last_rate_limits
except (OSError, UnicodeDecodeError) as exc:
logger.warning("failed to read codex session %s: %s", path, exc)
return None
last_rate_limits = last_general_rate_limits or last_rate_limits
if last_rate_limits is None:
return None
rate_limits, updated_at = last_rate_limits
Expand Down Expand Up @@ -997,6 +1007,7 @@ def _extract_rate_limits(path: Path, models: dict[str, str]) -> CodexRateLimits
has_credits=credits.get("has_credits") is True,
credit_balance=_as_str(credits.get("balance")) or None,
credits_unlimited=credits.get("unlimited") is True,
limit_id=_as_str(rate_limits.get("limit_id")),
)


Expand Down
50 changes: 50 additions & 0 deletions i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@
"talent_search_placeholder": "搜尋團隊",
"talent_no_results": "搜尋不到符合的團隊",
"switch_panel": "更換面板",
"language_menu": "語言",
"language_auto": "自動(依系統)",
"language_zh_cn": "简体中文",
"language_zh_tw": "繁體中文",
"language_en": "English",
"language_ja": "日本語",
"language_ko": "한국어",
"close_to_tray": "關閉到系統匣",
"talent_launch": "啟動",
"talent_back": "返回",
"talent_restore": "修復",
Expand Down Expand Up @@ -188,6 +196,7 @@
"service_partial_outage": "部分中斷",
"service_major_outage": "大規模中斷",
"today_text": "今日:${cost} ({tokens} tokens)",
"yesterday_text": "昨日:${cost} ({tokens} tokens)",
"percent_used": "{value}% 已用",
"reset_in": "重置 {time}",
"reset_imminent": "即將重置",
Expand Down Expand Up @@ -218,6 +227,7 @@
"history_load_error_tooltip": "目前顯示的是上次成功讀到的資料,等問題排除後會自動恢復。",
"projects_title": "專案用量",
"project_range_1d": "今日",
"project_range_yesterday": "昨日",
"project_range_7d": "7 日",
"project_range_30d": "月",
"project_range_all": "全部",
Expand Down Expand Up @@ -609,6 +619,14 @@
"talent_search_placeholder": "Search teams",
"talent_no_results": "No matching teams found",
"switch_panel": "Switch Panel",
"language_menu": "Language",
"language_auto": "Auto (System)",
"language_zh_cn": "简体中文",
"language_zh_tw": "繁體中文",
"language_en": "English",
"language_ja": "日本語",
"language_ko": "한국어",
"close_to_tray": "Close to tray",
"talent_launch": "Launch",
"talent_back": "Back",
"talent_restore": "Restore",
Expand Down Expand Up @@ -669,6 +687,7 @@
"service_partial_outage": "Partial outage",
"service_major_outage": "Major outage",
"today_text": "Today: ${cost} ({tokens} tokens)",
"yesterday_text": "Yesterday: ${cost} ({tokens} tokens)",
"percent_used": "{value}% used",
"reset_in": "Resets in {time}",
"reset_imminent": "Reset imminent",
Expand Down Expand Up @@ -699,6 +718,7 @@
"history_load_error_tooltip": "Showing the last successfully loaded data; this will recover automatically once resolved.",
"projects_title": "Project Usage",
"project_range_1d": "Today",
"project_range_yesterday": "Yesterday",
"project_range_7d": "7 Days",
"project_range_30d": "Month",
"project_range_all": "All Time",
Expand Down Expand Up @@ -1090,6 +1110,14 @@
"talent_search_placeholder": "搜索团队",
"talent_no_results": "搜索不到符合的团队",
"switch_panel": "切换面板",
"language_menu": "语言",
"language_auto": "自动(跟随系统)",
"language_zh_cn": "简体中文",
"language_zh_tw": "繁體中文",
"language_en": "English",
"language_ja": "日本語",
"language_ko": "한국어",
"close_to_tray": "关闭到托盘",
"talent_launch": "启动",
"talent_back": "返回",
"talent_restore": "修复",
Expand Down Expand Up @@ -1150,6 +1178,7 @@
"service_partial_outage": "部分中断",
"service_major_outage": "大规模中断",
"today_text": "今日:${cost} ({tokens} tokens)",
"yesterday_text": "昨天:${cost} ({tokens} tokens)",
"percent_used": "{value}% 已用",
"reset_in": "重置 {time}",
"reset_imminent": "即将重置",
Expand Down Expand Up @@ -1180,6 +1209,7 @@
"history_load_error_tooltip": "目前显示的是上次成功读到的数据,问题解决后会自动恢复。",
"projects_title": "项目用量",
"project_range_1d": "今日",
"project_range_yesterday": "昨天",
"project_range_7d": "7 日",
"project_range_30d": "月",
"project_range_all": "全部",
Expand Down Expand Up @@ -1571,6 +1601,14 @@
"talent_search_placeholder": "チームを検索",
"talent_no_results": "該当するチームが見つかりません",
"switch_panel": "パネル切替",
"language_menu": "言語",
"language_auto": "自動(システム)",
"language_zh_cn": "简体中文",
"language_zh_tw": "繁體中文",
"language_en": "English",
"language_ja": "日本語",
"language_ko": "한국어",
"close_to_tray": "トレイに閉じる",
"talent_launch": "起動",
"talent_back": "戻る",
"talent_restore": "修復",
Expand Down Expand Up @@ -1631,6 +1669,7 @@
"service_partial_outage": "一部障害",
"service_major_outage": "大規模障害",
"today_text": "今日: ${cost} ({tokens} tokens)",
"yesterday_text": "昨日: ${cost} ({tokens} tokens)",
"percent_used": "{value}% 使用済み",
"reset_in": "{time}後にリセット",
"reset_imminent": "まもなくリセット",
Expand Down Expand Up @@ -1661,6 +1700,7 @@
"history_load_error_tooltip": "現在表示中なのは前回正常に読み込めたデータです。問題が解消すると自動的に復旧します。",
"projects_title": "プロジェクト使用量",
"project_range_1d": "今日",
"project_range_yesterday": "昨日",
"project_range_7d": "7日間",
"project_range_30d": "月",
"project_range_all": "全期間",
Expand Down Expand Up @@ -2052,6 +2092,14 @@
"talent_search_placeholder": "팀 검색",
"talent_no_results": "일치하는 팀을 찾을 수 없습니다",
"switch_panel": "패널 전환",
"language_menu": "언어",
"language_auto": "자동 (시스템)",
"language_zh_cn": "简体中文",
"language_zh_tw": "繁體中文",
"language_en": "English",
"language_ja": "日本語",
"language_ko": "한국어",
"close_to_tray": "트레이로 닫기",
"talent_launch": "실행",
"talent_back": "뒤로",
"talent_restore": "복원",
Expand Down Expand Up @@ -2112,6 +2160,7 @@
"service_partial_outage": "부분 장애",
"service_major_outage": "대규모 장애",
"today_text": "오늘: ${cost} ({tokens} tokens)",
"yesterday_text": "어제: ${cost} ({tokens} tokens)",
"percent_used": "{value}% 사용됨",
"reset_in": "{time} 후 초기화",
"reset_imminent": "곧 초기화",
Expand Down Expand Up @@ -2142,6 +2191,7 @@
"history_load_error_tooltip": "마지막으로 정상적으로 불러온 데이터를 표시하고 있습니다. 문제가 해결되면 자동으로 복구됩니다.",
"projects_title": "프로젝트 사용량",
"project_range_1d": "오늘",
"project_range_yesterday": "어제",
"project_range_7d": "7일",
"project_range_30d": "월",
"project_range_all": "전체",
Expand Down
Loading