feat(ai-usage): 优化额度与重置进度展示
This commit is contained in:
1 parent
c7e4a34e91
commit
9a7e0cc1e0
4 files changed
+31
-11
No files matched your search
@@ -17,6 +17,7 @@ extension DS {
|
||||
static let success = Color.dynamic(light: 0x16A34A, dark: 0x22C55E)
|
||||
static let warning = Color.dynamic(light: 0xD97706, dark: 0xF59E0B)
|
||||
static let error = Color.dynamic(light: 0xDC2626, dark: 0xF87171)
|
||||
static let critical = Color.dynamic(light: 0x991B1B, dark: 0xEF4444)
|
||||
|
||||
// 文字
|
||||
static let textPrimary = Color.dynamic(light: 0x111827, dark: 0xF3F4F6)
|
||||
|
||||
@@ -139,21 +139,19 @@ private struct LocalUsageContent: View {
|
||||
.foregroundStyle(DS.Palette.textPrimary)
|
||||
}
|
||||
.dsFont(.xs)
|
||||
ProgressView(value: window.remainingPercent, total: 100)
|
||||
.tint(DS.Palette.primary)
|
||||
ProgressTrack(fraction: window.remainingPercent / 100,
|
||||
color: progressTint(for: window.remainingPercent))
|
||||
.accessibilityLabel("\(quotaTitle(window.kind)) \(tr("剩余")) \(Int(window.remainingPercent.rounded()))%")
|
||||
if let reset = window.resetsAt {
|
||||
Text(tr("重置:") + " " + reset.formatted(Date.FormatStyle(date: .abbreviated, time: .shortened, locale: L10n.locale)))
|
||||
.dsFont(.xs).foregroundStyle(DS.Palette.textTertiary)
|
||||
TimelineView(.periodic(from: .now, by: 60)) { context in
|
||||
if let fraction = window.remainingTimeFraction(at: context.date) {
|
||||
HStack(spacing: DS.Space.s2) {
|
||||
Text(tr("距重置"))
|
||||
.dsFont(.xs).foregroundStyle(DS.Palette.textTertiary)
|
||||
ProgressView(value: fraction, total: 1)
|
||||
.tint(DS.Palette.textTertiary)
|
||||
.accessibilityLabel(tr("距重置"))
|
||||
}
|
||||
// 时间条越接近重置越满、颜色越偏绿;额度条则随剩余量减少而变红。
|
||||
ProgressTrack(fraction: 1 - fraction,
|
||||
color: progressTint(for: (1 - fraction) * 100))
|
||||
.accessibilityLabel(tr("重置:") + " " + reset.formatted(Date.FormatStyle(date: .abbreviated, time: .shortened, locale: L10n.locale)))
|
||||
.accessibilityValue("\(Int(((1 - fraction) * 100).rounded()))%")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,6 +184,14 @@ private struct LocalUsageContent: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func progressTint(for percent: Double) -> Color {
|
||||
if percent <= 10 { return DS.Palette.critical }
|
||||
if percent <= 20 { return DS.Palette.error }
|
||||
if percent < 40 { return DS.Palette.warning }
|
||||
if percent < 80 { return DS.Palette.primary }
|
||||
return DS.Palette.success
|
||||
}
|
||||
|
||||
private func quotaFailureText(_ failure: AIQuotaFailure?) -> String {
|
||||
switch failure {
|
||||
case nil: tr("暂无额度数据")
|
||||
|
||||
@@ -57,6 +57,20 @@ private actor StubQuotaHTTPClient: QuotaHTTPClient {
|
||||
#expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer test-token")
|
||||
}
|
||||
|
||||
@Test func codexShowsOnlyQuotaWindowsReturnedForEachPlan() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_799_000_000)
|
||||
let plus = Data(#"{"plan_type":"plus","rate_limit":{"primary_window":{"used_percent":35,"limit_window_seconds":18000,"reset_after_seconds":3600},"secondary_window":null}}"#.utf8)
|
||||
let pro = Data(#"{"plan_type":"pro","rate_limit":{"primary_window":{"used_percent":74,"limit_window_seconds":604800,"reset_after_seconds":86400},"secondary_window":null}}"#.utf8)
|
||||
|
||||
let plusSnapshot = try CodexQuotaProvider.parse(plus, now: now)
|
||||
#expect(plusSnapshot.window(.session)?.remainingPercent == 65)
|
||||
#expect(plusSnapshot.window(.weekly) == nil)
|
||||
|
||||
let proSnapshot = try CodexQuotaProvider.parse(pro, now: now)
|
||||
#expect(proSnapshot.window(.session) == nil)
|
||||
#expect(proSnapshot.window(.weekly)?.remainingPercent == 26)
|
||||
}
|
||||
|
||||
@Test func claudeMapsFiveHourAndWeeklyBuckets() async throws {
|
||||
let http = StubQuotaHTTPClient(body: #"{"five_hour":{"utilization":22.5,"resets_at":"2026-09-23T10:00:00Z"},"seven_day":{"utilization":72,"resets_at":"2026-09-29T10:00:00Z"},"seven_day_sonnet":{"utilization":81,"resets_at":"2026-09-29T10:00:00Z"}}"#)
|
||||
let provider = ClaudeQuotaProvider(credentials: { "claude-test-token" }, http: http)
|
||||
|
||||
@@ -78,8 +78,7 @@ SOFTWARE.
|
||||
|
||||
## AI Provider 契约参考
|
||||
|
||||
XStats 的 AI Provider 契约与状态设计参考了以下项目公开的 Provider 契约、测试样例与实现模式;
|
||||
XStats 未引入这些项目的可执行程序、Sparkle 更新模块或浏览器扩展,也不读取任何登录凭据:
|
||||
XStats 的 AI Provider 契约与状态设计参考了以下项目公开的 Provider 契约、测试样例与实现模式:
|
||||
|
||||
- [burakgon/ai-usage-menubar](https://github.com/burakgon/ai-usage-menubar),MIT License,Copyright (c) 2026 Burak Gon
|
||||
- [robinebers/openusage](https://github.com/robinebers/openusage),MIT License,Copyright (c) 2026 Robin Ebers
|
||||
|
||||
Reference in new issue
Block a user