mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-15 03:24:49 +00:00
Kimi Code tokens live ~15 min and only the CLI renews them, so the load state sits in .terminalFailure as its dominant steady state between CLI uses. The Plan tab and tab-strip chip flapped to a reconnect screen every cycle even with a good last snapshot on hand. Extract the display decision into a pure KimiQuotaPresentation helper: terminal-with-data shows the usage bars (flagged idle) and only the no-data case falls through to reconnect. loadedBody stamps an 'as of <time>' caption once a snapshot is older than 10 min, in every state that renders it. The chip's kimiQuotaSummary downgrades terminal-with-data to .stale so the bar and popover keep the last-known rows instead of the reconnect card. Settings' connect/disconnect pane still explains the terminal reason. Adds KimiQuotaPresentationTests.
51 lines
2.2 KiB
Swift
51 lines
2.2 KiB
Swift
import Foundation
|
|
|
|
/// Pure display-decision helpers for the Kimi Code quota surfaces.
|
|
///
|
|
/// Kimi Code tokens live ~15 min and only the Kimi CLI renews them, so the
|
|
/// load state sits in `.terminalFailure` as its dominant steady state between
|
|
/// CLI uses. The always-visible surfaces (Plan tab, tab-strip chip) must keep
|
|
/// showing the last good snapshot with a quiet caption instead of flapping to
|
|
/// a reconnect screen every cycle. The reconnect screen is reserved for the
|
|
/// no-data case, where there is genuinely nothing to show.
|
|
enum KimiQuotaPresentation {
|
|
/// Which Plan-tab subview to render, given the load state and whether a
|
|
/// last-known snapshot exists.
|
|
enum PlanContent: Equatable {
|
|
case noCredentials
|
|
case loading
|
|
case failed
|
|
case transientFailed
|
|
case reconnect(reason: String?)
|
|
/// Render the loaded usage bars. `idle` is true when the login has
|
|
/// gone terminal but a snapshot is still on hand — the caller stamps a
|
|
/// quiet "run the CLI" caption instead of hiding the data.
|
|
case usage(idle: Bool)
|
|
}
|
|
|
|
static func planContent(loadState: SubscriptionLoadState, hasUsage: Bool) -> PlanContent {
|
|
switch loadState {
|
|
case .notBootstrapped, .noCredentials:
|
|
return .noCredentials
|
|
case .dormant, .bootstrapping:
|
|
return .loading
|
|
case .loading, .loaded:
|
|
return hasUsage ? .usage(idle: false) : .loading
|
|
case .failed:
|
|
return .failed
|
|
case .transientFailure:
|
|
return hasUsage ? .usage(idle: false) : .transientFailed
|
|
case .terminalFailure(let reason):
|
|
return hasUsage ? .usage(idle: true) : .reconnect(reason: reason)
|
|
}
|
|
}
|
|
|
|
/// Snapshot age past which a loaded view stamps an "as of <time>" caption,
|
|
/// so a bar can never silently masquerade as current. Kimi's ~15-min token
|
|
/// life makes 10 minutes the point where a snapshot is likely a cycle old.
|
|
static let stalenessThreshold: TimeInterval = 10 * 60
|
|
|
|
static func isStale(fetchedAt: Date, now: Date = Date()) -> Bool {
|
|
now.timeIntervalSince(fetchedAt) > stalenessThreshold
|
|
}
|
|
}
|