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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,15 @@ TokenStepSwift/dist/TokenStep.app
Developer ID 签名:

```bash
TOKENSTEP_VERSION=0.1.45 \
TOKENSTEP_VERSION=0.1.46 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
./script/package_release.sh
```

签名 + Apple 公证:

```bash
TOKENSTEP_VERSION=0.1.45 \
TOKENSTEP_VERSION=0.1.46 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
./script/package_release.sh --notarize
Expand Down
159 changes: 89 additions & 70 deletions TokenStepSwift/Sources/TokenStepSwift/Models/UsageModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -581,92 +581,124 @@ struct CodexQuotaWindow: Equatable, Identifiable, Codable {

struct TokenRankLeaderboard: Equatable {
var fetchedAt: Date
var board: String
var range: String
var client: String
var usageMode: String
var totalTokens: Int
var totalRankedUsers: Int
var topLimit: Int
var entries: [TokenRankEntry]

var topEntry: TokenRankEntry? {
entries.first
}

func entry(matching userID: String) -> TokenRankEntry? {
let normalizedID = TokenStepSettings.cleanedTokenRankUserID(userID)
guard !normalizedID.isEmpty else { return nil }
return entries.first { $0.userID == normalizedID }
func entry(matching userID: Int) -> TokenRankEntry? {
entries.first { $0.userID == userID }
}
}

struct TokenRankEntry: Decodable, Equatable, Identifiable {
var id: String { userID }
var id: Int { userID }
var rank: Int
var userID: String
var userID: Int
var name: String
var avatar: String?
var score: Int
var cost: Double
var byTool: [String: Int]
var avatarURL: String?
var totalTokens: Int
var callCount: Int
var sessionCount: Int
var clients: [String: Int]
var models: [String: Int]

enum CodingKeys: String, CodingKey {
case rank
case userID = "userId"
case name
case avatar
case score
case cost
case byTool
case user
case totalTokens = "total_tokens"
case callCount = "call_count"
case sessionCount = "session_count"
case clients
case models
}

init(
rank: Int,
userID: String,
userID: Int,
name: String,
avatar: String?,
score: Int,
cost: Double,
byTool: [String: Int]
avatarURL: String?,
totalTokens: Int,
callCount: Int,
sessionCount: Int,
clients: [String: Int],
models: [String: Int]
) {
self.rank = rank
self.userID = userID
self.name = name
self.avatar = avatar
self.score = score
self.cost = cost
self.byTool = byTool
self.avatarURL = avatarURL
self.totalTokens = totalTokens
self.callCount = callCount
self.sessionCount = sessionCount
self.clients = clients
self.models = models
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let user = try container.decode(TokenRankPublicUser.self, forKey: .user)
rank = try container.decode(Int.self, forKey: .rank)
userID = try Self.decodeFlexibleString(from: container, forKey: .userID)
name = try container.decodeIfPresent(String.self, forKey: .name) ?? L("匿名用户")
avatar = try container.decodeIfPresent(String.self, forKey: .avatar)
score = try container.decode(Int.self, forKey: .score)
cost = try container.decodeIfPresent(Double.self, forKey: .cost) ?? 0
byTool = try container.decodeIfPresent([String: Int].self, forKey: .byTool) ?? [:]
}

private static func decodeFlexibleString(
from container: KeyedDecodingContainer<CodingKeys>,
forKey key: CodingKeys
) throws -> String {
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decode(Int.self, forKey: key) {
return String(value)
}
if let value = try? container.decode(Int64.self, forKey: key) {
return String(value)
}
return ""
userID = user.id
name = user.name
avatarURL = user.avatarURL
totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) ?? 0
callCount = try container.decodeIfPresent(Int.self, forKey: .callCount) ?? 0
sessionCount = try container.decodeIfPresent(Int.self, forKey: .sessionCount) ?? 0
clients = try container.decodeIfPresent([String: Int].self, forKey: .clients) ?? [:]
models = try container.decodeIfPresent([String: Int].self, forKey: .models) ?? [:]
}
}

private struct TokenRankPublicUser: Decodable {
var id: Int
var name: String
var avatarURL: String?

enum CodingKeys: String, CodingKey {
case id
case name
case avatarURL = "avatar_url"
}
}

struct TokenRankLeaderboardResponse: Decodable {
var status: Int?
var board: String
var success: Bool
var data: TokenRankLeaderboardPayload
}

struct TokenRankLeaderboardPayload: Decodable {
var range: String
var entries: [TokenRankEntry]
var client: String
var usageMode: String
var totalTokens: Int
var totalRankedUsers: Int
var topLimit: Int
var rows: [TokenRankEntry]

enum CodingKeys: String, CodingKey {
case range
case client
case usageMode = "usage_mode"
case totalTokens = "total_tokens"
case totalRankedUsers = "total_ranked_users"
case topLimit = "top_limit"
case rows
}
}

struct AgentWorkRankIdentity: Equatable, Identifiable {
var id: Int
var name: String
var avatarURL: String?
var lastSyncedAt: Date?
}

enum TokenIslandDisplayPlacement: String, CaseIterable, Identifiable, Codable {
Expand Down Expand Up @@ -764,9 +796,8 @@ struct TokenStepSettings: Codable {
var tokenIslandEnabled: Bool
var tokenIslandPlacement: TokenIslandDisplayPlacement
var showCodexQuota: Bool
var showTokenRank: Bool
var showAgentWorkRank: Bool
var showExperimentalAgentSources: Bool
var tokenRankUserID: String
var language: TokenStepLanguage
var skippedUpdateVersion: String?

Expand All @@ -781,9 +812,8 @@ struct TokenStepSettings: Codable {
case tokenIslandEnabled = "token_island_enabled"
case tokenIslandPlacement = "token_island_placement"
case showCodexQuota = "show_codex_quota"
case showTokenRank = "show_token_rank"
case showAgentWorkRank = "show_agent_work_rank"
case showExperimentalAgentSources = "show_experimental_agent_sources"
case tokenRankUserID = "token_rank_user_id"
case language
case skippedUpdateVersion = "skipped_update_version"
}
Expand All @@ -799,9 +829,8 @@ struct TokenStepSettings: Codable {
tokenIslandEnabled: false,
tokenIslandPlacement: .menuBar,
showCodexQuota: false,
showTokenRank: false,
showAgentWorkRank: false,
showExperimentalAgentSources: false,
tokenRankUserID: "",
language: .system,
skippedUpdateVersion: nil
)
Expand All @@ -817,9 +846,8 @@ struct TokenStepSettings: Codable {
tokenIslandEnabled: Bool,
tokenIslandPlacement: TokenIslandDisplayPlacement,
showCodexQuota: Bool,
showTokenRank: Bool,
showAgentWorkRank: Bool,
showExperimentalAgentSources: Bool,
tokenRankUserID: String,
language: TokenStepLanguage,
skippedUpdateVersion: String?
) {
Expand All @@ -833,19 +861,12 @@ struct TokenStepSettings: Codable {
self.tokenIslandEnabled = tokenIslandEnabled
self.tokenIslandPlacement = tokenIslandPlacement
self.showCodexQuota = showCodexQuota
self.showTokenRank = showTokenRank
self.showAgentWorkRank = showAgentWorkRank
self.showExperimentalAgentSources = showExperimentalAgentSources
self.tokenRankUserID = Self.cleanedTokenRankUserID(tokenRankUserID)
self.language = language
self.skippedUpdateVersion = skippedUpdateVersion
}

static func cleanedTokenRankUserID(_ value: String) -> String {
value
.trimmingCharacters(in: .whitespacesAndNewlines)
.filter(\.isNumber)
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let defaults = TokenStepSettings.defaults
Expand All @@ -867,10 +888,8 @@ struct TokenStepSettings: Codable {
tokenIslandPlacement = defaults.tokenIslandPlacement
}
showCodexQuota = try container.decodeIfPresent(Bool.self, forKey: .showCodexQuota) ?? defaults.showCodexQuota
showTokenRank = try container.decodeIfPresent(Bool.self, forKey: .showTokenRank) ?? defaults.showTokenRank
showAgentWorkRank = try container.decodeIfPresent(Bool.self, forKey: .showAgentWorkRank) ?? defaults.showAgentWorkRank
showExperimentalAgentSources = try container.decodeIfPresent(Bool.self, forKey: .showExperimentalAgentSources) ?? defaults.showExperimentalAgentSources
let decodedTokenRankUserID = try container.decodeIfPresent(String.self, forKey: .tokenRankUserID) ?? defaults.tokenRankUserID
tokenRankUserID = Self.cleanedTokenRankUserID(decodedTokenRankUserID)
language = try container.decodeIfPresent(TokenStepLanguage.self, forKey: .language) ?? defaults.language
skippedUpdateVersion = try container.decodeIfPresent(String.self, forKey: .skippedUpdateVersion)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,8 @@ enum DataService {
tokenIslandEnabled: placement != .menuBar,
tokenIslandPlacement: placement,
showCodexQuota: settings.showCodexQuota,
showTokenRank: settings.showTokenRank,
showAgentWorkRank: settings.showAgentWorkRank,
showExperimentalAgentSources: settings.showExperimentalAgentSources,
tokenRankUserID: TokenStepSettings.cleanedTokenRankUserID(settings.tokenRankUserID),
language: settings.language,
skippedUpdateVersion: settings.skippedUpdateVersion
)
Expand Down
Loading
Loading