Skip to content
Open
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
6 changes: 3 additions & 3 deletions index.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": 1,
"updated_at": "2026-06-25T06:01:55.000Z",
"updated_at": "2026-06-27T10:36:45.000Z",
"scripts": [
{
"id": "codex-context-ring-restore",
Expand Down Expand Up @@ -72,7 +72,7 @@
"id": "codex-daily-token-usage",
"name": "Codex Daily Token Usage",
"description": "每日 Token 统计,近 5 日滚动存储,自动复用已有采集,支持 Model 价格、成本估算、趋势明细和分享图",
"version": "1.4.5",
"version": "1.4.6",
"author": "kts-kris",
"tags": [
"codex",
Expand All @@ -84,7 +84,7 @@
],
"homepage": "",
"script_url": "https://raw.githubusercontent.com/BigPizzaV3/CodexPlusPlusScriptMarket/main/scripts/codex-daily-token-usage.js",
"sha256": "b565fe5a5728d52700d4d089d874a3f3e7f258a82fdea69762155ebbdf1c2c2f"
"sha256": "40d0edb049467391ba6c1c40d7712feaaa1427ba187ea46441ec14680ff55592"
},
{
"id": "codex-list-pagebuster",
Expand Down
141 changes: 134 additions & 7 deletions scripts/codex-daily-token-usage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Codex Daily Token Usage
// @namespace codex-plus-plus
// @version 1.4.5
// @version 1.4.6
// @description 每日 Token 统计,近 5 日滚动存储,优先复用已有采集,必要时内置采集,支持 Model 价格、成本估算、日期切换、5 日趋势与分享图。
// @match app://-/*
// @run-at document-start
Expand All @@ -10,7 +10,7 @@
(() => {
"use strict";

const VERSION = "1.4.5";
const VERSION = "1.4.6";
const API_KEY = "__codexDailyTokenUsage";
const SOURCE_API_KEY = "__codexTokenUsage";
const STORAGE_KEY = "__codexDailyTokenUsageV1";
Expand All @@ -29,7 +29,12 @@
const UNKNOWN_MODEL = "Unknown";
const PRICE_FIELDS = ["input", "cachedInput", "output", "reasoning"];
const FLOATING_TOP = 2;
const FLOATING_RIGHT = 280;
const FLOATING_DEFAULT_RIGHT = 280;
const FLOATING_SAFE_GAP = 8;
const FLOATING_SCAN_TOP = 96;
const FLOATING_MIN_WIDTH = 94;
const FLOATING_COMPACT_WIDTH = 31;
const FLOATING_HEIGHT = 31;
const PANEL_GAP = 8;
const PANEL_MARGIN = 12;
const WINDOW_BUTTON_SAFE_RIGHT = 132;
Expand Down Expand Up @@ -1551,7 +1556,7 @@
#${ROOT_ID}.codex-daily-floating {
position: fixed;
top: ${FLOATING_TOP}px;
right: ${FLOATING_RIGHT}px;
right: ${FLOATING_DEFAULT_RIGHT}px;
bottom: auto;
left: auto;
}
Expand Down Expand Up @@ -1589,6 +1594,12 @@
#${ROOT_ID}.is-updated .codex-daily-trigger {
animation: codex-daily-token-pulse 420ms ease;
}
#${ROOT_ID}[data-layout="compact-icon"] .codex-daily-trigger {
width: ${FLOATING_COMPACT_WIDTH}px;
min-width: ${FLOATING_COMPACT_WIDTH}px;
padding: 0;
gap: 0;
}
#${ROOT_ID} .codex-daily-sigma {
width: 16px;
height: 16px;
Expand All @@ -1605,6 +1616,10 @@
font-weight: 600;
letter-spacing: 0.01em;
}
#${ROOT_ID}[data-layout="compact-icon"] .codex-daily-label,
#${ROOT_ID}[data-layout="compact-icon"] .codex-daily-total {
display: none;
}
#${PANEL_ID} {
position: fixed;
width: min(350px, calc(100vw - 24px));
Expand Down Expand Up @@ -2117,7 +2132,7 @@
root.innerHTML = `
<button class="codex-daily-trigger" type="button" aria-expanded="false" aria-label="查看今日 Token 用量">
<span class="codex-daily-sigma" aria-hidden="true">Σ</span>
<span>今日</span>
<span class="codex-daily-label">今日</span>
<span class="codex-daily-total">0</span>
</button>
`;
Expand Down Expand Up @@ -2471,6 +2486,106 @@
hidePanel();
}

function normalizeRect(rect) {
if (!rect) return null;
const left = Number(rect.left);
const top = Number(rect.top);
const right = Number(rect.right);
const bottom = Number(rect.bottom);
if (![left, top, right, bottom].every(Number.isFinite)) return null;
const width = Math.max(0, right - left);
const height = Math.max(0, bottom - top);
if (!width || !height) return null;
return { left, top, right, bottom, width, height };
}

function rectsOverlap(first, second, gap = 0) {
return (
first.left < second.right + gap &&
first.right > second.left - gap &&
first.top < second.bottom + gap &&
first.bottom > second.top - gap
);
}

function candidateRectFromRight(right, top, width, height, viewportWidth) {
const left = viewportWidth - right - width;
return { left, right: left + width, top, bottom: top + height, width, height };
}

function resolveFloatingLayout(width, height, viewportWidth, viewportHeight, obstacleRects = []) {
const safeWidth = Math.min(Math.max(1, Number(width) || FLOATING_MIN_WIDTH), Math.max(1, viewportWidth - PANEL_MARGIN * 2));
const compactWidth = Math.min(FLOATING_COMPACT_WIDTH, safeWidth);
const safeHeight = Math.max(1, Number(height) || FLOATING_HEIGHT);
const top = FLOATING_TOP;
const maxRight = Math.max(PANEL_MARGIN, viewportWidth - PANEL_MARGIN - safeWidth);
const clampRight = (right) => Math.min(Math.max(PANEL_MARGIN, right), maxRight);
const layoutFromLeft = (left, width, compact = false) => ({
top,
right: viewportWidth - left - width,
left,
width,
compact,
});
const topObstacles = obstacleRects
.map(normalizeRect)
.filter((rect) => rect && rect.bottom > 0 && rect.top < FLOATING_SCAN_TOP && rect.right > 0 && rect.left < viewportWidth);
const defaultRight = clampRight(FLOATING_DEFAULT_RIGHT);
const defaultRect = candidateRectFromRight(defaultRight, top, safeWidth, safeHeight, viewportWidth);
if (!topObstacles.some((rect) => rectsOverlap(defaultRect, rect, FLOATING_SAFE_GAP))) {
return { top, right: defaultRight, left: defaultRect.left, width: safeWidth, compact: false };
}

const blocked = topObstacles
.map((rect) => ({
left: Math.max(PANEL_MARGIN, rect.left - FLOATING_SAFE_GAP),
right: Math.min(viewportWidth - PANEL_MARGIN, rect.right + FLOATING_SAFE_GAP),
}))
.sort((a, b) => a.left - b.left);
let cursor = PANEL_MARGIN;
const gaps = [];
for (const rect of blocked) {
if (rect.left > cursor) gaps.push({ left: cursor, right: rect.left });
cursor = Math.max(cursor, rect.right);
}
if (cursor < viewportWidth - PANEL_MARGIN) gaps.push({ left: cursor, right: viewportWidth - PANEL_MARGIN });

const fullGap = gaps
.filter((gap) => gap.right - gap.left >= safeWidth)
.sort((a, b) => b.right - a.right)[0];
if (fullGap) {
return layoutFromLeft(fullGap.right - safeWidth, safeWidth, false);
}

const compactGap = gaps
.filter((gap) => gap.right - gap.left >= compactWidth)
.sort((a, b) => b.right - a.right)[0];
if (compactGap) {
return layoutFromLeft(compactGap.right - compactWidth, compactWidth, true);
}

const fallbackRight = Math.min(Math.max(PANEL_MARGIN, defaultRight), Math.max(PANEL_MARGIN, viewportWidth - PANEL_MARGIN - compactWidth));
const fallbackRect = candidateRectFromRight(fallbackRight, top, compactWidth, safeHeight, viewportWidth);
return { top, right: fallbackRight, left: fallbackRect.left, width: compactWidth, compact: true };
}

function collectTopObstacleRects() {
if (!document.body?.querySelectorAll) return [];
const selector = "button,[role='button'],input,select,textarea,#codex-plus-menu,[data-testid]";
return Array.from(document.body.querySelectorAll(selector))
.filter((node) => !root?.contains(node) && !panel?.contains(node))
.map((node) => {
const rect = normalizeRect(node.getBoundingClientRect?.());
if (!rect || rect.width < 4 || rect.height < 4) return null;
if (rect.top >= FLOATING_SCAN_TOP || rect.bottom <= 0) return null;
if (rect.width > innerWidth * 0.85 || rect.height > FLOATING_SCAN_TOP) return null;
const style = typeof getComputedStyle === "function" ? getComputedStyle(node) : null;
if (style && (style.display === "none" || style.visibility === "hidden" || style.opacity === "0")) return null;
return rect;
})
.filter(Boolean);
}

function findToolbar() {
return null;

Expand Down Expand Up @@ -2518,11 +2633,21 @@
if (root.parentElement !== document.body) {
document.body.appendChild(root);
}
root.style.top = `${FLOATING_TOP}px`;
root.style.right = `${FLOATING_RIGHT}px`;
root.dataset.layout = "top-toolbar";
const rect = root.getBoundingClientRect();
const layout = resolveFloatingLayout(
rect.width || FLOATING_MIN_WIDTH,
rect.height || FLOATING_HEIGHT,
innerWidth,
innerHeight,
collectTopObstacleRects()
);
root.style.top = `${Math.round(layout.top)}px`;
root.style.right = `${Math.round(layout.right)}px`;
root.style.left = "auto";
root.style.bottom = "auto";
root.style.transform = "none";
root.dataset.layout = layout.compact ? "compact-icon" : "top-toolbar";
if (panel?.classList.contains("is-visible")) positionPanel();
}

Expand Down Expand Up @@ -2787,6 +2912,8 @@
trendPoints,
trendPath,
buildShareModel,
resolveFloatingLayout,
rectsOverlap,
findUsageCandidates,
processCapturePayload,
processModelPayload,
Expand Down