Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

TokenStep 各版本的更新内容,新版本在前。每个版本的完整说明在 [docs/RELEASE_NOTES_<版本号>.md](docs/)。

## 0.2.19 更新:额度与排名更精简

额度条粗细统一,Cursor 模型额度也有时间进度竖线;排名只在前 10 名时显示前三名,改为显示离你最近的后一名;大数字的单位更紧凑,页脚更简洁。详见 [0.2.19 发布说明](docs/RELEASE_NOTES_0.2.19.md)。

## 0.2.18 更新:额度预警写明窗口

额度提前用完的提示会写出是哪个窗口,例如「7 天额度会提前用完」,不再和快要重置的 5 小时窗口混淆;面板里的排名可以直接打开 Token Rank 榜单和个人主页。详见 [0.2.18 发布说明](docs/RELEASE_NOTES_0.2.18.md)。
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TokenStep 是一个 macOS 菜单栏 App,用来本地统计你在 Codex、Claud

下载最新版 DMG,打开后把 `TokenStep.app` 拖进「应用程序」即可使用:

[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.18.dmg)
[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.19.dmg)

也可以从 Release 页面查看所有版本:

Expand All @@ -32,9 +32,9 @@ TokenStep 已使用 Developer ID 签名并通过 Apple 公证。首次打开时

Windows版本由十七做了移植,欢迎大家前往使用:https://github.com/canyexuanfan/TokenStep-Windows/releases

## 最新版本:0.2.18
## 最新版本:0.2.19

额度提前用完的提示会写出是哪个窗口,例如「7 天额度会提前用完」;面板里的排名可以直接打开 Token Rank 网页。0.2.16 起菜单栏面板改为一列紧凑面板,额度刷新也更及时。详见 [0.2.18 发布说明](docs/RELEASE_NOTES_0.2.18.md)。
面板的额度和排名更精简:额度条粗细统一,排名只在前 10 名时显示前三名,并显示离你最近的后一名。0.2.16 起菜单栏面板改为一列紧凑面板,额度刷新更及时。详见 [0.2.19 发布说明](docs/RELEASE_NOTES_0.2.19.md)。

历次更新(主题包、引力动效、自动更新等)见 [CHANGELOG.md](CHANGELOG.md)。

Expand Down Expand Up @@ -95,7 +95,7 @@ TokenStep 默认只做本地统计。

## 安装方式

1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.18.dmg)。
1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.19.dmg)。
2. 打开 DMG。
3. 把 `TokenStep.app` 拖到「应用程序」。
4. 启动 TokenStep。
Expand Down Expand Up @@ -166,7 +166,7 @@ TokenStepSwift/dist/TokenStep.app
公开发布强制执行 Developer ID 签名、Apple 公证、票据装订、系统分发检查和隔离安装验证。不再生成可被误上传的“仅签名、未公证”发布包:

```bash
TOKENSTEP_VERSION=0.2.18 \
TOKENSTEP_VERSION=0.2.19 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
./script/package_release.sh --notarize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ enum QuotaWindowKind: String, Codable, Equatable {
case .monthlyCredits: return L("本月额度")
case .tokenWindow: return L("Token 窗")
case .spend: return L("花费")
case .cursorModels: return L("Cursor 模型")
case .cursorModels: return L("自带")
case .otherModels: return L("其他模型")
}
}
Expand All @@ -67,7 +67,7 @@ enum QuotaWindowKind: String, Codable, Equatable {
case .monthlyCredits: return L("本月")
case .tokenWindow: return L("Token")
case .spend: return L("花费")
case .cursorModels: return L("自有")
case .cursorModels: return L("自带")
case .otherModels: return L("其他")
}
}
Expand Down
18 changes: 6 additions & 12 deletions TokenStepSwift/Sources/TokenStepSwift/Support/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,6 @@ enum TokenStepLocalization {
"保存浮层 PNG": "Save popover PNG",
"截取浮层": "Capture popover",
"细线是每日目标": "Thin line marks the daily goal",
"手动刷新": "Manual refresh",
"刷新 %@": "Refresh %@",
"打开仪表盘": "Open Dashboard",
"退出": "Quit",
"青绿": "Green",
Expand Down Expand Up @@ -498,7 +496,6 @@ enum TokenStepLocalization {
"再跑": "Run",
"分享": "Share",
"另有 %d 项正常:%@": "%d more, all normal: %@",
"差 %@": "%@ ahead",
"已用": "Used",
"已用 · %@": "Used · %@",
"引力动效": "Gravity motion",
Expand All @@ -513,7 +510,6 @@ enum TokenStepLocalization {
"今日还没有你的上传": "No uploads from you today yet",
"本地比榜单多 %@,同步后预计升到 #%d": "%@ more locally than on the board; about #%d after sync",
"本地比榜单多 %@,等待同步": "%@ more locally than on the board, waiting for sync",
"榜上 %@": "On board %@",
"榜单 %@": "Board %@",
"照此速度会提前用完": "Runs out early at this pace",
"%@额度会提前用完": "%@ runs out early",
Expand All @@ -528,6 +524,8 @@ enum TokenStepLocalization {
"进入前 %d 名": "to enter the top %d",
"退出 TokenStep": "Quit TokenStep",
"领先第二名 %@": "%@ ahead of #2",
"身后 #%d %@": "Behind you: #%d %@",
"领先 %@": "%@ ahead",
"近似 · %d 会话": "Approx. · %d sessions",
"近似值(按会话开始日汇总)": "Approximate (totals by session start day)",
"版本": "Ver",
Expand Down Expand Up @@ -573,9 +571,8 @@ enum TokenStepLocalization {
"需要 Coding Plan 的 ZAI_API_KEY": "Needs a Coding Plan ZAI_API_KEY",
"需要 ~/.kimi 的 OAuth,不要用开放平台 key": "Needs ~/.kimi OAuth, not a Moonshot platform key",
"需要 grok login 产生的 ~/.grok/auth.json": "Needs ~/.grok/auth.json from grok login",
"Cursor 模型": "Cursor models",
"其他模型": "Other models",
"自有": "Included",
"自带": "Included",
"圈数进度,颜色不按来源分段": "Lap progress only. Color is not split by source.",
"Cursor 无本地 token,额度见右栏": "Cursor has no local tokens. See quotas on the right.",
"Cursor 无本地 token,不在此列": "Cursor has no local tokens, so it is not listed here.",
Expand Down Expand Up @@ -1085,8 +1082,6 @@ enum TokenStepLocalization {
"保存浮层 PNG": "儲存浮層 PNG",
"截取浮层": "擷取浮層",
"细线是每日目标": "細線是每日目標",
"手动刷新": "手動重新整理",
"刷新 %@": "重新整理 %@",
"打开仪表盘": "開啟儀表板",
"退出": "退出",
"青绿": "青綠",
Expand Down Expand Up @@ -1196,7 +1191,6 @@ enum TokenStepLocalization {
"再跑": "再跑",
"分享": "分享",
"另有 %d 项正常:%@": "另有 %d 項正常:%@",
"差 %@": "差 %@",
"已用": "已用",
"已用 · %@": "已用 · %@",
"引力动效": "引力動效",
Expand All @@ -1211,7 +1205,6 @@ enum TokenStepLocalization {
"今日还没有你的上传": "今日還沒有你的上傳",
"本地比榜单多 %@,同步后预计升到 #%d": "本機比榜單多 %@,同步後預計升到 #%d",
"本地比榜单多 %@,等待同步": "本機比榜單多 %@,等待同步",
"榜上 %@": "榜上 %@",
"榜单 %@": "榜單 %@",
"照此速度会提前用完": "照此速度會提前用完",
"%@额度会提前用完": "%@額度會提前用完",
Expand All @@ -1226,6 +1219,8 @@ enum TokenStepLocalization {
"进入前 %d 名": "進入前 %d 名",
"退出 TokenStep": "結束 TokenStep",
"领先第二名 %@": "領先第二名 %@",
"身后 #%d %@": "身後 #%d %@",
"领先 %@": "領先 %@",
"近似 · %d 会话": "近似 · %d 會話",
"近似值(按会话开始日汇总)": "近似值(按會話開始日彙總)",
"版本": "版本",
Expand Down Expand Up @@ -1296,9 +1291,8 @@ enum TokenStepLocalization {
"清除密钥只在你往钥匙串里存过内容时才出现。Grok 正常走 ~/.grok/auth.json,一般看不到这个按钮。": "清除密鑰只在你往鑰匙圈裡存過內容時才出現。Grok 正常走 ~/.grok/auth.json,一般看不到這個按鈕。",
"Grok Build 弹出的短码是给终端用的。贴回正在跑 grok login 的窗口,等它显示成功,再点重新检测。": "Grok Build 彈出的短碼是給終端用的。貼回正在跑 grok login 的視窗,等它顯示成功,再點重新檢測。",
"一般不用填,短码不要贴到这里": "一般不用填,短碼不要貼到這裡",
"Cursor 模型": "Cursor 模型",
"其他模型": "其他模型",
"自有": "自有",
"自带": "自帶",
"圈数进度,颜色不按来源分段": "圈數進度,顏色不按來源分段",
"Cursor 无本地 token,额度见右栏": "Cursor 無本地 token,額度見右欄",
"Cursor 无本地 token,不在此列": "Cursor 無本地 token,不在此列",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,10 @@ enum CompactPopoverModel {
return resetsAt.addingTimeInterval(-5 * 60 * 60)
case .sevenDay, .weekly:
return resetsAt.addingTimeInterval(-7 * 24 * 60 * 60)
case .monthlyCredits:
case .monthlyCredits, .cursorModels, .otherModels:
// Cursor's model pools reset with its monthly billing cycle.
return calendar.date(byAdding: .month, value: -1, to: resetsAt)
case .session, .tokenWindow, .spend, .cursorModels, .otherModels:
case .session, .tokenWindow, .spend:
return nil
}
}
Expand Down Expand Up @@ -198,14 +199,23 @@ enum CompactPopoverModel {
/// Token Rank's public board counts days in this zone.
static let rankBoardTimeZoneIdentifier = "Asia/Shanghai"

/// The podium is only worth showing to viewers near it; further down the
/// board the gap to the top is too large to act on.
static let podiumRankLimit = 10

struct Podium: Equatable, Identifiable {
var id: Int { rank }
var rank: Int
var name: String
var tokens: Int
var isMe: Bool
/// How far ahead of the viewer this entry is; nil when unknown or not ahead.
var leadOverMe: Int?
}

/// The closest entry behind the viewer, and by how much the viewer leads it.
struct Behind: Equatable {
var rank: Int
var name: String
var lead: Int
}

enum Chase: Equatable {
Expand All @@ -222,8 +232,10 @@ enum CompactPopoverModel {
var rankedUsers: Int
var listedLimit: Int
var myBoardTokens: Int?
/// Empty unless the viewer ranks within `podiumRankLimit`.
var podium: [Podium]
var chase: Chase?
var behind: Behind?
/// Minutes to close the chase gap at the last hour's pace.
var etaMinutes: Int?
var tokensPerHour: Int
Expand All @@ -242,29 +254,32 @@ enum CompactPopoverModel {
) -> RankSection {
let entries = board.entries.sorted { $0.rank < $1.rank }
let mine = myUserID.flatMap { id in entries.first { $0.userID == id } }
let podium = entries.prefix(3).map { entry in
Podium(
rank: entry.rank,
name: entry.name,
tokens: entry.totalTokens,
isMe: entry.userID == mine?.userID,
leadOverMe: mine.flatMap { me in
entry.rank < me.rank ? max(entry.totalTokens - me.totalTokens, 0) : nil
}
)
}
let podium = (mine.map { $0.rank <= podiumRankLimit } ?? false)
? entries.prefix(3).map { entry in
Podium(rank: entry.rank, name: entry.name, tokens: entry.totalTokens, isMe: entry.userID == mine?.userID)
}
: []

let sameDay = localTimeZoneIdentifier == rankBoardTimeZoneIdentifier
var chase: Chase?
var behind: Behind?
var gap: Int?
if let mine {
if let next = entries.last(where: { $0.rank < mine.rank }) {
let needed = max(next.totalTokens - mine.totalTokens, 0) + 1
// Local tokens run ahead of the board between uploads; on the same
// day they are the truer count to measure gaps from.
let myTokens = sameDay ? max(mine.totalTokens, localTodayTokens) : mine.totalTokens
let others = entries.filter { $0.userID != mine.userID }
if let next = others.last(where: { $0.totalTokens >= myTokens }) {
let needed = next.totalTokens - myTokens + 1
gap = needed
let progress = next.totalTokens > 0 ? Double(mine.totalTokens) / Double(next.totalTokens) : 0
let progress = next.totalTokens > 0 ? Double(myTokens) / Double(next.totalTokens) : 0
chase = .next(rank: next.rank, name: next.name, gap: needed, progress: min(max(progress, 0), 1))
} else if let second = entries.first(where: { $0.rank > mine.rank }) {
chase = .leading(lead: max(mine.totalTokens - second.totalTokens, 0))
} else if let second = others.first {
chase = .leading(lead: max(myTokens - second.totalTokens, 0))
}
// First place already shows its lead over second.
if case .next = chase, let below = others.first(where: { $0.totalTokens < myTokens }) {
behind = Behind(rank: below.rank, name: below.name, lead: myTokens - below.totalTokens)
}
} else if myUserID != nil, let last = entries.last, entries.count >= board.topLimit {
// Not listed: the board only returns the top `topLimit`. Local tokens are
Expand Down Expand Up @@ -296,6 +311,7 @@ enum CompactPopoverModel {
myBoardTokens: mine?.totalTokens,
podium: podium,
chase: chase,
behind: behind,
etaMinutes: eta,
tokensPerHour: tokensPerHour,
unsyncedTokens: unsynced,
Expand Down
Loading
Loading