• lazyhang

    之前一直用的额度统计脚本今天突然用不了 额度换算出来都是0

    图片

    问gpt说是官方接口的 totals、clients 还是 models,**credits 全部精确等于 0.0

    让gpt5.6调试了一下搞了一个新脚本出来 亲测可用 (不建议用了不是很精准)

    图片```
    (async () => {
    "use strict";

    // ============================================================
    // Codex Quota Compass V2.3
    // 多模型费率反校正版 · 纯实测 · 无套餐倍率
    //
    // 数据来源:
    //
    // 1. /backend-api/wham/usage
    // -> 实时 weekly used_percent / reset
    //
    // 2. /backend-api/wham/usage/daily-token-usage-breakdown
    // -> 每天每个模型消耗的 allowance 百分点
    //
    // 3. /backend-api/wham/analytics/daily-workspace-usage-counts
    // -> 每日总 uncached / cached / output token
    // -> 每模型 turns(只展示,不用于价格分配)
    //
    // 个人账号没有 per-model token breakdown。
    //
    // 多模型日算法:
    //
    // 当天 Token 结构:
    // fu = uncached / total
    // fc = cached / total
    // fo = output / total
    //
    // 模型综合费率:
    // Rm = fu * inputRate
    // + fc * cachedRate
    // + fo * outputRate
    //
    // OpenAI 给出的该模型 allowance 百分点 = Pm
    //
    // Token 权重:
    // Wm = Pm / Rm
    //
    // 模型 Token share:
    // Sm = Wm / sum(W)
    //
    // 再按 Sm 分配当天三类总 Token。
    //
    // 这样可以保证昂贵模型不会因为 quota 占比高
    // 就被错误分配同样比例的 Token。
    //
    // 注意:
    // - 完全没有 Plus×1 / ProLite×5 / Pro×20
    // - plan_type 只显示
    // - 忽略 >272K long-context multiplier
    // - 所有价格始终使用基础 Token 单价
    // ============================================================

    const CONFIG = {
    HISTORY_DAYS: 45,

    USD_PER_CREDIT: 0.04,

    // allowance 百分比太小的日子容易受统计舍入影响。
    MIN_DAY_PERCENT: 0.10,

    // 同一周期多个反推结果的允许离散。
    CLUSTER_TOLERANCE: 0.10,

    // ==========================================================
    // OpenAI 基础 API / ChatGPT Work Token rate card
    // USD / 1M tokens
    //
    // 按用户要求:
    // 不考虑 >272K context 的 2x / 1.5x 加价。
    // ==========================================================

    MODEL_RATES: {
    "gpt-6-astra": {
    input: 10.00,
    cached: 1.00,
    output: 50.00,
    },

    "gpt-5.6-sol": {
    input: 4.00,
    cached: 0.40,
    output: 20.00,
    },

    "gpt-5.6-terra": {
    input: 2.00,
    cached: 0.20,
    output: 12.00,
    },

    "gpt-5.6-luna": {
    input: 0.20,
    cached: 0.02,
    output: 1.20,
    },

    "gpt-5.5": {
    input: 5.00,
    cached: 0.50,
    output: 30.00,
    },

    "gpt-5.4": {
    input: 2.50,
    cached: 0.25,
    output: 15.00,
    },

    "gpt-5.4-mini": {
    input: 0.75,
    cached: 0.075,
    output: 4.50,
    },

    "gpt-5.3-codex": {
    input: 1.75,
    cached: 0.175,
    output: 14.00,
    },

    "gpt-5.2": {
    input: 1.75,
    cached: 0.175,
    output: 14.00,
    },

    "daybreak-blue": {
    input: 4.00,
    cached: 0.40,
    output: 20.00,
    },

    "daybreak-red": {
    input: 12.50,
    cached: 1.25,
    output: 75.00,
    },
    },
    };

    // ============================================================
    // 基础工具
    // ============================================================

    const n = (v) => {
    const x = Number(v);
    return Number.isFinite(x) ? x : 0;
    };

    const clamp = (v, min, max) =>
    Math.min(max, Math.max(min, v));

    const fmtMoney = (v) =>
    Number.isFinite(v)
    ? $${v.toFixed(2)}
    : "—";

    const fmtPct = (v, d = 3) =>
    Number.isFinite(v)
    ? ${v.toFixed(d)}%
    : "—";

    const fmtNum = (v) => {
    if (!Number.isFinite(v)) return "—";

    if (v >= 1e9)
    return ${(v / 1e9).toFixed(2)}B;

    if (v >= 1e6)
    return ${(v / 1e6).toFixed(2)}M;

    if (v >= 1e3)
    return ${(v / 1e3).toFixed(2)}K;

    return Math.round(v).toLocaleString();
    };

    const dayKey = (ms) =>
    new Date(ms).toISOString().slice(0, 10);

    const addDays = (key, days) => {
    const d = new Date(${key}T00:00:00Z);
    d.setUTCDate(d.getUTCDate() + days);
    return d.toISOString().slice(0, 10);
    };

    function median(values) {
    const a = values
    .filter(Number.isFinite)
    .sort((x, y) => x - y);

    if (!a.length) return null;

    const i = Math.floor(a.length / 2);

    return a.length % 2
    ? a[i]
    : (a[i - 1] + a[i]) / 2;
    }

    // ============================================================
    // Auth
    // ============================================================

    function getAccessToken() {
    const boot =
    document.getElementById("client-bootstrap")
    ?.textContent || "";

    return (
    boot.match(
    /[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/
    )?.[0] || null
    );
    }

    async function apiGet(path, token) {
    const res = await fetch(path, {
    credentials: "include",

    headers: {
    Authorization: Bearer ${token},
    Accept: "application/json",
    },
    });

    const text = await res.text();

    if (!res.ok) {
    throw new Error(
    HTTP ${res.status}: ${text.slice(0, 300)}
    );
    }

    return JSON.parse(text);
    }

    // ============================================================
    // Model helpers
    // ============================================================

    function normalizeModel(model) {
    return String(model || "")
    .trim()
    .toLowerCase();
    }

    function getRate(model, speed = "standard") {
    // 这里故意不把 Fast 套用 Standard 价格。
    // 当前脚本只精确处理 standard。
    if (speed && speed !== "standard") {
    return null;
    }

    return (
    CONFIG.MODEL_RATES[
    normalizeModel(model)
    ] || null
    );
    }

    function getModelTurns(countRow) {
    const result = new Map();

    for (const m of countRow?.models || []) {
    const model = normalizeModel(m.model);

    result.set(
    model,
    (result.get(model) || 0) +
    n(m.turns)
    );
    }

    return result;
    }

    // ============================================================
    // 多模型反向分配
    // ============================================================

    function estimateDay(countRow, pctRow) {
    const totals = countRow?.totals;

    if (!totals) {
    return {
    valid: false,
    reason: "Token 尚未结算",
    };
    }

    const uncached =
    n(totals.uncached_text_input_tokens);

    const cached =
    n(totals.cached_text_input_tokens);

    const output =
    n(totals.text_output_tokens);

    const total =
    uncached + cached + output;

    if (!(total > 0)) {
    return {
    valid: false,
    reason: "无 Token",
    };
    }

    // 当天 Token 类型结构。
    const fu = uncached / total;
    const fc = cached / total;
    const fo = output / total;

    const modelRows = (pctRow?.models || [])
    .map((m) => ({
    model: normalizeModel(m.model),
    speed: m.speed || "standard",
    percent: n(m.credits),
    }))
    .filter((m) => m.percent > 0);

    const totalPercent =
    modelRows.reduce(
    (sum, m) => sum + m.percent,
    0
    );

    if (!(totalPercent > 0)) {
    return {
    valid: false,
    reason: "无 allowance 百分比",
    };
    }

    if (
    totalPercent <
    CONFIG.MIN_DAY_PERCENT
    ) {
    return {
    valid: false,
    reason: "allowance 样本太小",
    };
    }

    const turns =
    getModelTurns(countRow);

    const priced = [];
    const unknown = [];

    for (const m of modelRows) {
    const rate =
    getRate(m.model, m.speed);

    if (!rate) {
    unknown.push(m);
    continue;
    }

    // 当前一天的综合费率。
    // USD / 1M “当天平均结构 Token”
    const compositeRate =
    fu * rate.input +
    fc * rate.cached +
    fo * rate.output;

    if (!(compositeRate > 0)) {
    continue;
    }

    priced.push({
    ...m,
    rate,
    compositeRate,

    // 关键:
    //
    // quota share ≈ token * price
    //
    // 所以:
    //
    // token weight ≈ quota share / price
    tokenWeight:
    m.percent /
    compositeRate,
    });
    }

    if (!priced.length) {
    return {
    valid: false,
    reason: "当天模型价格均未知",
    };
    }

    const knownPct =
    priced.reduce(
    (sum, m) => sum + m.percent,
    0
    );

    // 为了避免未知模型偷偷导致金额偏低,
    // 只有所有 allowance 都能定价时
    // 才允许用于周额度反推。
    const coverage =
    knownPct / totalPercent;

    if (coverage < 0.999) {
    return {
    valid: false,
    reason:
    价格覆盖仅 ${(coverage * 100).toFixed(2)}%,
    unknown,
    };
    }

    const weightTotal =
    priced.reduce(
    (sum, m) =>
    sum + m.tokenWeight,
    0
    );

    if (!(weightTotal > 0)) {
    return {
    valid: false,
    reason: "无法计算 Token 权重",
    };
    }

    let dayUsd = 0;

    const allocatedModels =
    priced.map((m) => {
    const share =
    m.tokenWeight /
    weightTotal;

    // 假设各模型当天 uncached/cache/output
    // 结构与当天总结构一致。
    //
    // 个人接口没有更细的数据,
    // 这是必要的估算假设。
    const mu =
    uncached * share;

    const mc =
    cached * share;

    const mo =
    output * share;

    const modelUsd =
    (mu / 1e6) *
    m.rate.input +
    (mc / 1e6) *
    m.rate.cached +
    (mo / 1e6) *
    m.rate.output;

    dayUsd += modelUsd;

    return {
    model: m.model,
    speed: m.speed,

    allowancePercent:
    m.percent,

    turns:
    turns.get(m.model) || 0,

    tokenShare:
    share,

    uncached:
    mu,

    cached:
    mc,

    output:
    mo,

    totalTokens:
    mu + mc + mo,

    compositeRate:
    m.compositeRate,

    usd:
    modelUsd,
    };
    });

    // ==========================================================
    // 一整份 allowance 的等价美元价值
    // ==========================================================

    const allowanceUsd =
    dayUsd /
    (totalPercent / 100);

    return {
    valid: true,

    date:
    countRow.date,

    totalPercent,

    uncached,
    cached,
    output,
    totalTokens:
    total,

    dayUsd,

    allowanceUsd,

    models:
    allocatedModels,

    estimatedAllocation:
    allocatedModels.length > 1,

    unknown,
    };
    }

    // ============================================================
    // 合并 daily 两个接口
    // ============================================================

    function buildDays(counts, percents) {
    const countMap =
    new Map(
    (counts?.data || []).map(
    (x) => [x.date, x]
    )
    );

    const pctMap =
    new Map(
    (percents?.data || []).map(
    (x) => [x.date, x]
    )
    );

    const dates = [
    ...new Set([
    ...countMap.keys(),
    ...pctMap.keys(),
    ]),
    ].sort();

    return dates.map((date) => {
    const count =
    countMap.get(date) || null;

    const pct =
    pctMap.get(date) || null;

    const result =
    estimateDay(count, pct);

    return {
    date,
    count,
    pct,
    ...result,
    };
    });
    }

    // ============================================================
    // 当前周期额度聚合
    // ============================================================

    function estimateCycle(rows) {
    const usable =
    rows.filter(
    (r) =>
    r.valid &&
    Number.isFinite(r.allowanceUsd) &&
    r.allowanceUsd > 0
    );

    if (!usable.length) {
    return null;
    }

    if (usable.length === 1) {
    return {
    value:
    usable[0].allowanceUsd,

    samples: 1,
    spread: 0,

    rows: usable,
    };
    }

    const initial =
    median(
    usable.map(
    (r) => r.allowanceUsd
    )
    );

    const cluster =
    usable.filter(
    (r) =>
    Math.abs(
    r.allowanceUsd -
    initial
    ) /
    initial <=
    CONFIG.CLUSTER_TOLERANCE
    );

    if (!cluster.length) {
    return null;
    }

    // 按当天消耗百分比加权。
    // 额度消耗越大的日子统计误差通常越小。
    const weight =
    cluster.reduce(
    (sum, r) =>
    sum + r.totalPercent,
    0
    );

    const weighted =
    cluster.reduce(
    (sum, r) =>
    sum +
    r.allowanceUsd *
    r.totalPercent,
    0
    ) / weight;

    const values =
    cluster.map(
    (r) => r.allowanceUsd
    );

    const spread =
    values.length > 1
    ? (
    Math.max(...values) -
    Math.min(...values)
    ) /
    weighted
    : 0;

    return {
    value: weighted,

    samples:
    cluster.length,

    spread,

    dropped:
    usable.length -
    cluster.length,

    rows:
    cluster,
    };
    }

    // ============================================================
    // UI
    // ============================================================

    function addStyle() {
    document
    .getElementById(
    "cqv23-style"
    )
    ?.remove();

    const style =
    document.createElement("style");

    style.id = "cqv23-style";

    style.textContent = `
    #cqv23 {
    position: fixed;
    top: 2vh;
    left: 50%;
    transform: translateX(-50%);

    width: min(1180px, calc(100vw - 24px));
    max-height: 96vh;
    overflow: auto;

    z-index: 2147483647;

    background: white;
    color: #222;

    border: 1px solid #ddd;
    border-radius: 14px;

    padding: 20px;

    box-shadow:
    0 18px 70px rgba(0,0,0,.28);

    font-family:
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
    }

    #cqv23 * {
    box-sizing: border-box;
    }

    #cqv23 .head {
    display: flex;
    justify-content: space-between;
    align-items: center;

    margin-bottom: 14px;
    }

    #cqv23 .title {
    font-size: 19px;
    font-weight: 700;
    }

    #cqv23 .close {
    font-size: 28px;
    cursor: pointer;
    color: #888;
    }

    #cqv23 .cards {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 10px;
    margin-bottom: 14px;
    }

    #cqv23 .card {
    border: 1px solid #ececec;
    background: #f7f7f7;

    border-radius: 9px;
    padding: 11px;
    }

    #cqv23 .card.good {
    background: #eefaf5;
    border-color: #cfeee1;
    }

    #cqv23 .card.used {
    background: #fff8ea;
    border-color: #f4dfad;
    }

    #cqv23 .label {
    color: #777;
    font-size: 11px;
    margin-bottom: 5px;
    }

    #cqv23 .value {
    font-size: 16px;
    font-weight: 750;
    color: #0f8f70;
    white-space: nowrap;
    }

    #cqv23 .card.used .value {
    color: #a35d00;
    }

    #cqv23 .sub {
    color: #777;
    font-size: 10px;
    margin-top: 4px;
    }

    #cqv23 .note {
    background: #fafafa;
    border: 1px solid #eee;
    border-radius: 8px;

    padding: 10px 12px;
    margin-bottom: 14px;

    font-size: 12px;
    line-height: 1.6;
    color: #555;
    }

    #cqv23 .tablebox {
    max-height: 390px;
    overflow: auto;

    border: 1px solid #eee;
    border-radius: 8px;

    margin-bottom: 14px;
    }

    #cqv23 table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
    }

    #cqv23 th,
    #cqv23 td {
    padding: 7px 6px;
    border-bottom: 1px solid #eee;
    text-align: left;
    white-space: nowrap;
    }

    #cqv23 th {
    position: sticky;
    top: 0;
    z-index: 1;

    background: #f5f5f5;
    color: #666;
    }

    #cqv23 .goodtext {
    color: #0f8f70;
    font-weight: 600;
    }

    #cqv23 .warn {
    color: #a35d00;
    }

    #cqv23 .muted {
    color: #999;
    }

    #cqv23 details {
    margin-top: 5px;
    }

    #cqv23 summary {
    cursor: pointer;
    }

    @media(max-width: 900px) {
    #cqv23 .cards {
    grid-template-columns: repeat(3,1fr);
    }
    }
    `;

    document.head.appendChild(style);
    }

    function render({
    usage,
    rows,
    cycleStartDate,
    estimate,
    }) {
    document
    .getElementById("cqv23")
    ?.remove();

    addStyle();

    const root =
    document.createElement("div");

    root.id = "cqv23";

    const window =
    usage?.rate_limit
    ?.secondary_window ||
    usage?.rate_limit
    ?.primary_window;

    const livePercent =
    clamp(
    n(window?.used_percent),
    0,
    100
    );

    const weeklyUsd =
    estimate?.value ?? null;

    const usedUsd =
    weeklyUsd != null
    ? weeklyUsd *
    livePercent /
    100
    : null;

    const leftUsd =
    weeklyUsd != null
    ? weeklyUsd - usedUsd
    : null;

    const weeklyCredits =
    weeklyUsd != null
    ? weeklyUsd /
    CONFIG.USD_PER_CREDIT
    : null;

    const usedCredits =
    usedUsd != null
    ? usedUsd /
    CONFIG.USD_PER_CREDIT
    : null;

    const currentRows =
    rows.filter(
    (r) =>
    r.date >= cycleStartDate
    );

    const settledPct =
    currentRows.reduce(
    (sum, r) =>
    sum + n(r.totalPercent),
    0
    );

    const rowsHtml =
    [...rows]
    .sort(
    (a, b) =>
    b.date.localeCompare(a.date)
    )
    .map((r) => {
    const current =
    r.date >= cycleStartDate;

    if (!r.valid) {
    return `
    <tr>
    <td>${current ? "● " : ""}${r.date}</td>
    <td>${fmtPct(
    (r.pct?.models || [])
    .reduce(
    (s,m) => s+n(m.credits),
    0
    )
    )}</td>
    <td colspan="4" class="muted">
    ${r.reason || "无可用数据"}
    </td>
    </tr>
    `;
    }

    const modelText =
    r.models
    .map(
    (m) =>
    ${m.model.replace("gpt-","")} +
    ${fmtPct(m.allowancePercent)}
    )
    .join(" / ");

    return `
    <tr>
    <td>
    ${current ? "● " : ""}
    ${r.date}
    </td>

    <td>
    ${fmtPct(r.totalPercent)}
    </td>

    <td>
    ${modelText}
    </td>

    <td>
    ${fmtNum(r.totalTokens)}
    </td>

    <td>
    ${fmtMoney(r.dayUsd)}
    </td>

    <td class="goodtext">
    ${fmtMoney(r.allowanceUsd)}
    ${
    r.estimatedAllocation
    ? " ≈"
    : ""
    }
    </td>
    </tr>
    `;
    })
    .join("");

    const modelDetails =
    currentRows
    .filter((r) => r.valid)
    .flatMap((r) =>
    r.models.map((m) => ({
    date: r.date,
    ...m,
    }))
    )
    .map(
    (m) => `
    <tr>
    <td>${m.date}</td>
    <td>${m.model.replace("gpt-","")}</td>
    <td>${m.turns}</td>
    <td>${fmtPct(m.allowancePercent)}</td>
    <td>${(m.tokenShare * 100).toFixed(2)}%</td>
    <td>${fmtNum(m.uncached)}</td>
    <td>${fmtNum(m.cached)}</td>
    <td>${fmtNum(m.output)}</td>
    <td>${fmtNum(m.totalTokens)}</td>
    <td>${fmtMoney(m.usd)}</td>
    </tr>
    `
    )
    .join("");

    root.innerHTML = `
    <div class="head">
    <div class="title">
    📊 Codex Quota Compass V2.3 · 多模型费率反校正
    </div>

    <div
    class="close"
    id="cqv23-close"

    ×
    </div>
    </div>

    <div class="cards">

    <div class="card">
    <div class="label">
    当前计划
    </div>

    <div class="value">
    ${usage?.plan_type || "unknown"}
    </div>

    <div class="sub">
    仅展示,不参与任何额度计算
    </div>
    </div>

    <div class="card">
    <div class="label">
    实时周额度已用
    </div>

    <div class="value">
    ${livePercent.toFixed(1)}%
    </div>

    <div class="sub">
    每日接口已结算 ${settledPct.toFixed(3)}%
    </div>
    </div>

    <div class="card good">
    <div class="label">
    当前完整周额度
    </div>

    <div class="value">
    ${
    weeklyUsd != null
    ? fmtMoney(weeklyUsd)
    : "等待当天 Token"
    }
    </div>

    <div class="sub">
    ${
    estimate
    ? ${estimate.samples} 个当前周期真实日样本
    : "无套餐倍率 / 无历史兜底"
    }
    </div>
    </div>

    <div class="card used">
    <div class="label">
    本周已用等价价值
    </div>

    <div class="value">
    ${
    usedUsd != null
    ? fmtMoney(usedUsd)
    : "—"
    }
    </div>

    <div class="sub">
    ${
    usedCredits != null
    ? ${fmtNum(usedCredits)} Credits eq.
    : "—"
    }
    </div>
    </div>

    <div class="card">
    <div class="label">
    本周剩余等价价值
    </div>

    <div class="value">
    ${
    leftUsd != null
    ? fmtMoney(leftUsd)
    : "—"
    }
    </div>
    </div>

    <div class="card">
    <div class="label">
    周总等价 Credits
    </div>

    <div class="value">
    ${
    weeklyCredits != null
    ? fmtNum(weeklyCredits)
    : "—"
    }
    </div>

    <div class="sub">
    $0.04 / Credit 仅作换算展示
    </div>
    </div>

    </div>

    <div class="note">
    <b>算法:</b>
    多模型日期不会再被丢弃,也不会简单按调用次数或额度百分比直接分 Token。
    脚本先用当天真实 uncached / cached / output 结构计算每个模型的
    <b>综合 API 费率</b>,再按照
    <code>模型 allowance% ÷ 综合费率</code>
    反向估算模型 Token 权重。

    <br><br>

    每模型的 <b>turns 是接口真实调用轮数</b>,
    但不会拿 turns 直接计价,因为不同调用的 Token 长度差异可能极大。

    <br>

    混模日的 per-model Token 属于
    <b>费率校正估算</b>;
    当天只有一个模型时,三类 Token 则无需分配,属于直接值。

    <br>

    <b>长上下文加价已完全忽略。</b>
    无论单次请求是否超过 272K,本脚本都只使用基础 API Token rate card。
    </div>

    <div class="tablebox">
    <table>
    <thead>
    <tr>
    <th>日期</th>
    <th>Allowance%</th>
    <th>模型占比</th>
    <th>总 Tokens</th>
    <th>当日等价$</th>
    <th>反推完整周$</th>
    </tr>
    </thead>

    <tbody>
    ${rowsHtml}
    </tbody>
    </table>
    </div>

    <details>
    <summary>
    🤖 查看当前周期各模型估算用量
    </summary>

    <div class="tablebox" style="margin-top:8px">
    <table>
    <thead>
    <tr>
    <th>日期</th>
    <th>模型</th>
    <th>实际 Turns</th>
    <th>Allowance%</th>
    <th>估算 Token 占比</th>
    <th>未缓存输入</th>
    <th>缓存输入</th>
    <th>输出</th>
    <th>总 Token</th>
    <th>等价$</th>
    </tr>
    </thead>

    <tbody>
    ${
    modelDetails ||
    `<tr>
    <td colspan="10" class="muted">
    当前周期 Token 尚未结算
    </td>
    </tr>`
    }
    </tbody>
    </table>
    </div>
    </details>

    <div class="note" style="margin-top:14px;margin-bottom:0">
    当前周期开始:
    <b>${cycleStartDate}</b>

    ${
    estimate
    ? `
    <br>
    当前周期有效样本:
    <b>${estimate.samples}</b> 天;
    完整周额度:
    <b>${fmtMoney(estimate.value)}</b>;
    样本离散:
    <b>${(estimate.spread * 100).toFixed(2)}%</b>。
    `
    : `
    <br>
    当前周期目前没有同时拥有
    <b>日 Token + 日 allowance%</b>
    的完整样本,因此还无法确定分母。
    这不是套餐倍率问题,而是当天 Token 数据尚未落库。
    `
    }
    </div>
    `;

    document.body.appendChild(root);

    document
    .getElementById("cqv23-close")
    .onclick = () => {
    root.remove();
    document
    .getElementById("cqv23-style")
    ?.remove();
    };
    }

    // ============================================================
    // MAIN
    // ============================================================

    try {
    console.log(
    "[Quota Compass V2.3] 获取登录信息..."
    );

    const token =
    getAccessToken();

    if (!token) {
    throw new Error(
    "无法取得 ChatGPT access token"
    );
    }

    const usage =
    await apiGet(
    "/backend-api/wham/usage",
    token
    );

    const window =
    usage?.rate_limit
    ?.secondary_window ||
    usage?.rate_limit
    ?.primary_window;

    if (!window) {
    throw new Error(
    "没有找到 weekly rate limit window"
    );
    }

    const now = Date.now();

    const today =
    dayKey(now);

    const startDate =
    dayKey(
    now -
    CONFIG.HISTORY_DAYS *
    86400000
    );

    const endDate =
    addDays(today, 1);

    const range =
    start_date=${encodeURIComponent(startDate)} +
    &end_date=${encodeURIComponent(endDate)} +
    &group_by=day;

    const cycleStartMs =
    (
    n(window.reset_at) -
    n(window.limit_window_seconds)
    ) * 1000;

    const cycleStartDate =
    dayKey(cycleStartMs);

    console.log(
    "[Quota Compass V2.3] 当前周期:",
    cycleStartDate
    );

    const [
    counts,
    percents,
    ] = await Promise.all([
    apiGet(
    "/backend-api/wham/analytics/" +
    "daily-workspace-usage-counts?" +
    range +
    "&workspace_user=true",
    token
    ),

    apiGet(
    "/backend-api/wham/usage/" +
    "daily-token-usage-breakdown?" +
    range,
    token
    ),
    ]);

    const rows =
    buildDays(
    counts,
    percents
    );

    const currentRows =
    rows.filter(
    (r) =>
    r.date >= cycleStartDate
    );

    const estimate =
    estimateCycle(
    currentRows
    );

    console.table(
    rows
    .filter(
    (r) =>
    r.valid ||
    (
    r.pct?.models ||
    []
    ).some(
    (m) =>
    n(m.credits) > 0
    )
    )
    .map((r) => ({
    date:
    r.date,

    current:
    r.date >= cycleStartDate,

    allowance_pct:
    r.totalPercent || 0,

    tokens:
    r.totalTokens || 0,

    day_usd:
    r.dayUsd
    ? +r.dayUsd.toFixed(3)
    : null,

    full_week_usd:
    r.allowanceUsd
    ? +r.allowanceUsd.toFixed(2)
    : null,

    result:
    r.valid
    ? (
    r.estimatedAllocation
    ? "费率校正"
    : "单模型直接值"
    )
    : r.reason,
    }))
    );

    if (estimate) {
    const live =
    clamp(
    n(window.used_percent),
    0,
    100
    );

    console.log(
    "====== 当前周实测 ======"
    );

    console.log(
    "周额度 API 等价价值:",
    estimate.value
    );

    console.log(
    "实时已用:",
    live + "%"
    );

    console.log(
    "本周已用等价价值:",
    estimate.value *
    live /
    100
    );

    console.log(
    "本周剩余等价价值:",
    estimate.value *
    (
    1 -
    live /
    100
    )
    );
    }

    render({
    usage,
    rows,
    cycleStartDate,
    estimate,
    });

    console.log(
    "[Quota Compass V2.3] ✅ 完成"
    );
    } catch (e) {
    console.error(
    "[Quota Compass V2.3]",
    e
    );

    alert(
    "Quota Compass V2.3 失败:\n\n" +
    (
    e?.message ||
    String(e)
    )
    );
    }
    })();

    主楼
  • lazyhang

    跟之前用法一样 打开额度界面 然后f12 控制台粘贴代码回车就行

    #1
  • YaSol

    图片

    15刀的Team周限171刀

    #2
  • lazyhang

    佬可以用一下我刚更新的 之前有一个问题计算的不是很精准

    #3
  • lililihy

    找不到这个页面的网址了。佬能发一下吗。

    #4
  • lazyhang

    Codex

    #5
  • starlight312

    图片

    pro 20x 只有这么点吗?我记得之前说一周有两千多?

    #6
  • starlight312

    https://linux.do/t/topic/2859464?u=starlight312 是不是要更新下 gpt6的额度统计不对

    #7
  • lazyhang

    在研究了 正在跟codex沟通适配gpt6

    #8
  • kkkk5

    图片

    在cpa中统计出来约1800刀全程只接cpa,没开上下文双倍计费,今天的就算没更新也不至于差这么多。毕竟今天就剩20%左右了

    #9
  • Vernon

    我是pro 5x,感觉CPA里和这个都不敢相信啊差距也太大了 ^-^

    图片

    图片

    #10
  • Ayuriko

    不准呐 pro20测出来600多刀

    #11

发表回复

登录后回复