Skip to content

Commit bb09504

Browse files
Zihao-QijoeVenner
authored andcommitted
Improve DeepSeek detailed usage
1 parent 59c0813 commit bb09504

38 files changed

Lines changed: 2570 additions & 95 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
- Linux CLI: bootstrap the configured IANA timezone before Foundation startup on non-FHS systems, preventing SIGILL on NixOS (#2127). Thanks @xikhar!
2929
- Ollama: release temporary dashboard network sessions after each fetch, preventing repeated refreshes from retaining delegates and URL-cache resources. Thanks @astuteprogrammer!
3030
- Amp: release temporary API and dashboard network sessions after every fetch, preventing repeated refreshes from retaining delegates and URL-cache resources.
31+
- DeepSeek: use a separate Platform web-session token for detailed usage, validate sessions across Chrome profiles,
32+
offer a picker when multiple profiles are valid, fetch amount and cost concurrently, and preserve API-key balance
33+
updates when detailed usage is unavailable.
3134
- Linux CLI: prevent usage rendering from crashing in Foundation bundle discovery when formatting rate windows. Thanks @thanthi-del!
3235
- CLI: defer login-shell PATH probes until Codex RPC launch, preserve login PATH for explicit script overrides, and reap session-escaped helpers without cross-probe descriptor inheritance. Thanks @anagnorisis2peripeteia!
3336
- Menus: keep overview provider-row clicks reliable during live menu rebuilds without stealing nested Copy or plan actions. Thanks @Yuxin-Qiao!

Sources/CodexBar/InlineUsageDashboardContent.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,19 @@ extension UsageMenuCardView.Model {
8686
]
8787
}
8888

89-
if input.provider == .deepseek,
90-
input.showOptionalCreditsAndExtraUsage,
91-
let usage = input.snapshot?.deepseekUsage
92-
{
89+
if input.provider == .deepseek, input.showOptionalCreditsAndExtraUsage {
90+
if input.isRefreshing {
91+
return []
92+
}
93+
guard let usage = input.snapshot?.deepseekUsage else {
94+
if input.snapshot?.deepseekDetailedUsageState == .webSessionRequired {
95+
return [L("Sign in to DeepSeek Platform in Chrome for detailed usage.")]
96+
}
97+
if input.snapshot?.deepseekDetailedUsageState == .profileSelectionRequired {
98+
return [L("Select a DeepSeek Chrome profile in Settings.")]
99+
}
100+
return [L("Detailed usage unavailable.")]
101+
}
93102
let symbol = usage.currency == "CNY" ? "¥" : "$"
94103
let todayCostStr = usage.todayCost.map { "\(symbol)\(String(format: "%.4f", max(0, $0)))" } ?? ""
95104
return [
@@ -226,6 +235,7 @@ extension UsageMenuCardView.Model {
226235
return Self.minimaxInlineDashboard(billing)
227236
}
228237
if input.provider == .deepseek,
238+
!input.isRefreshing,
229239
input.showOptionalCreditsAndExtraUsage,
230240
let usage = input.snapshot?.deepseekUsage,
231241
!usage.daily.isEmpty
@@ -673,7 +683,7 @@ extension UsageMenuCardView.Model {
673683
let monthTokensStr = UsageFormatter.tokenCountString(usage.currentMonthTokens)
674684

675685
return InlineUsageDashboardModel(
676-
accessibilityLabel: L("DeepSeek 30 day token usage trend"),
686+
accessibilityLabel: L("DeepSeek this month token usage trend"),
677687
valueStyle: .tokens,
678688
kpis: [
679689
.init(

Sources/CodexBar/PreferencesProviderDetailView.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
134134
provider: self.provider,
135135
model: self.model,
136136
openAIWebDiagnostic: self.openAIWebDiagnostic,
137-
isEnabled: self.isEnabled)
137+
isEnabled: self.isEnabled,
138+
isRefreshing: self.store.refreshingProviders.contains(self.provider))
138139
} header: {
139140
Text(L("Usage"))
140141
}
@@ -350,6 +351,7 @@ struct ProviderMetricsInlineView: View {
350351
let model: UsageMenuCardView.Model
351352
let openAIWebDiagnostic: String?
352353
let isEnabled: Bool
354+
let isRefreshing: Bool
353355

354356
struct InfoRow: Identifiable, Equatable {
355357
enum ID: Hashable {
@@ -430,10 +432,24 @@ struct ProviderMetricsInlineView: View {
430432
}
431433

432434
private var placeholderText: String {
433-
if !self.isEnabled {
435+
Self.placeholderText(
436+
isEnabled: self.isEnabled,
437+
isRefreshing: self.isRefreshing,
438+
modelPlaceholder: self.model.placeholder)
439+
}
440+
441+
static func placeholderText(
442+
isEnabled: Bool,
443+
isRefreshing: Bool,
444+
modelPlaceholder: String?) -> String
445+
{
446+
if !isEnabled {
434447
return L("Disabled — no recent data")
435448
}
436-
return self.model.placeholder.map(L) ?? L("No usage yet")
449+
if isRefreshing {
450+
return L("Refreshing")
451+
}
452+
return modelPlaceholder.map(L) ?? L("No usage yet")
437453
}
438454
}
439455

Sources/CodexBar/PreferencesProvidersPane.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ struct ProvidersPane: View {
163163
usageText = L("last_fetch_failed")
164164
} else if self.store.knownLimitsAvailability(for: provider)?.isUnavailable == true {
165165
usageText = L("Limits not available")
166-
} else if let snapshot = self.store.snapshot(for: provider) {
166+
} else if let snapshot = self.store.presentationSnapshot(for: provider) {
167167
let relative = snapshot.updatedAt.relativeDescription()
168168
usageText = relative
169169
} else {
@@ -189,7 +189,7 @@ struct ProvidersPane: View {
189189
L("last_fetch_failed")
190190
} else if self.store.knownLimitsAvailability(for: provider)?.isUnavailable == true {
191191
L("Limits not available")
192-
} else if let snapshot = self.store.snapshot(for: provider) {
192+
} else if let snapshot = self.store.presentationSnapshot(for: provider) {
193193
snapshot.updatedAt.relativeDescription()
194194
} else {
195195
L("usage_not_fetched_yet")
@@ -620,7 +620,7 @@ struct ProvidersPane: View {
620620

621621
func menuCardModel(for provider: UsageProvider) -> UsageMenuCardView.Model {
622622
let metadata = self.store.metadata(for: provider)
623-
let snapshot = self.store.snapshot(for: provider)
623+
let snapshot = self.store.presentationSnapshot(for: provider)
624624
let now = Date()
625625
let codexProjection = self.store.codexConsumerProjectionIfNeeded(
626626
for: provider,

Sources/CodexBar/ProviderRegistry.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ struct ProviderRegistry {
5656
runtime: .app,
5757
sourceMode: sourceMode,
5858
includeCredits: false,
59-
includeOptionalUsage: settings.showOptionalCreditsAndExtraUsage,
59+
includeOptionalUsage: ProviderTokenAccountSelection.shouldIncludeOptionalUsage(
60+
provider: provider,
61+
settings: settings,
62+
override: nil),
6063
webTimeout: 60,
6164
webDebugDumpHTML: false,
6265
verbose: verbose,

Sources/CodexBar/Providers/DeepSeek/DeepSeekProviderImplementation.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import CodexBarCore
22
import Foundation
3+
import SwiftUI
34

45
struct DeepSeekProviderImplementation: ProviderImplementation {
56
let id: UsageProvider = .deepseek
@@ -12,6 +13,54 @@ struct DeepSeekProviderImplementation: ProviderImplementation {
1213
@MainActor
1314
func observeSettings(_: SettingsStore) {}
1415

16+
@MainActor
17+
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
18+
let presentationSnapshot = context.store.presentationSnapshot(for: .deepseek)
19+
?? context.store.lastKnownResetSnapshots[.deepseek]
20+
let profiles = presentationSnapshot?.deepseekPlatformProfiles ?? []
21+
guard profiles.count > 1 || presentationSnapshot?.deepseekDetailedUsageState == .profileSelectionRequired
22+
else { return [] }
23+
let apiKey = context.settings.selectedTokenAccount(for: .deepseek)?.token
24+
?? DeepSeekSettingsReader.apiKey(environment: context.store.environmentBase)
25+
let selectedProfileID = context.settings.deepseekProfileID(apiKey: apiKey)
26+
let hasValidSelection = profiles.contains { $0.id == selectedProfileID }
27+
let profileBinding = Binding(
28+
get: {
29+
let profileID = context.settings.deepseekProfileID(apiKey: apiKey)
30+
return profiles.contains { $0.id == profileID } ? profileID : ""
31+
},
32+
set: { profileID in
33+
guard !profileID.isEmpty else { return }
34+
context.store.beginDeepSeekProfileTransition()
35+
context.settings.setDeepSeekProfileID(profileID, apiKey: apiKey)
36+
})
37+
let options = (hasValidSelection
38+
? []
39+
: [ProviderSettingsPickerOption(id: "", title: "Select profile…")])
40+
+ profiles.map { ProviderSettingsPickerOption(id: $0.id, title: $0.name) }
41+
42+
return [
43+
ProviderSettingsPickerDescriptor(
44+
id: "deepseek-chrome-profile",
45+
title: "Chrome profile",
46+
subtitle: "Choose which signed-in DeepSeek Platform session supplies detailed usage.",
47+
dynamicSubtitle: {
48+
context.store.refreshingProviders.contains(.deepseek)
49+
? "Refreshing"
50+
: nil
51+
},
52+
binding: profileBinding,
53+
options: options,
54+
isVisible: nil,
55+
isEnabled: { !context.store.refreshingProviders.contains(.deepseek) },
56+
onChange: { _ in
57+
await ProviderInteractionContext.$current.withValue(.userInitiated) {
58+
await context.store.refreshProvider(.deepseek, allowDisabled: true)
59+
}
60+
}),
61+
]
62+
}
63+
1564
@MainActor
1665
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
1766
if DeepSeekSettingsReader.apiKey(environment: context.environment) != nil {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import CodexBarCore
2+
import Foundation
3+
4+
extension SettingsStore {
5+
func deepseekProfileID(apiKey: String?) -> String {
6+
_ = self.configRevision
7+
_ = self.providerDetailSettingsRevision
8+
guard let config = self.config.providerConfig(for: .deepseek),
9+
let profileID = config.sanitizedDeepSeekProfileID
10+
else { return "" }
11+
let accountID = self.selectedTokenAccount(for: .deepseek)?.id
12+
let expectedScope = DeepSeekSettingsReader.profileScope(selectedTokenAccountID: accountID, apiKey: apiKey)
13+
guard let expectedScope, config.sanitizedDeepSeekProfileScope == expectedScope else { return "" }
14+
return profileID
15+
}
16+
17+
func setDeepSeekProfileID(_ newValue: String, apiKey: String?) {
18+
let profileID = self.normalizedConfigValue(newValue)
19+
let profileScope = DeepSeekSettingsReader.profileScope(
20+
selectedTokenAccountID: self.selectedTokenAccount(for: .deepseek)?.id,
21+
apiKey: apiKey)
22+
guard profileID == nil || profileScope != nil else { return }
23+
self.updateProviderDetailConfig(provider: .deepseek) { entry in
24+
entry.deepseekProfileID = profileID
25+
entry.deepseekProfileScope = profileID == nil ? nil : profileScope
26+
}
27+
}
28+
}

Sources/CodexBar/Providers/Shared/ProviderTokenAccountSelection.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,18 @@ enum ProviderTokenAccountSelection {
1616
if let override, override.provider == provider { return override.account }
1717
return settings.effectiveSelectedTokenAccount(for: provider)
1818
}
19+
20+
@MainActor
21+
static func shouldIncludeOptionalUsage(
22+
provider: UsageProvider,
23+
settings: SettingsStore,
24+
override: TokenAccountOverride?) -> Bool
25+
{
26+
guard settings.showOptionalCreditsAndExtraUsage else { return false }
27+
guard provider == .deepseek,
28+
let override,
29+
override.provider == provider
30+
else { return true }
31+
return settings.selectedTokenAccount(for: provider)?.id == override.account.id
32+
}
1933
}

Sources/CodexBar/Resources/ar.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,13 @@
902902
"MiniMax 30 day token usage trend" = "MiniMax اتجاه استخدام الرموز خلال 30 يوما";
903903
"Today cash" = "اليوم نقدا";
904904
"DeepSeek 30 day token usage trend" = "DeepSeek اتجاه استخدام الرموز خلال 30 يوما";
905+
"Detailed usage unavailable." = "الاستخدام التفصيلي غير متاح.";
906+
"Sign in to DeepSeek Platform in Chrome for detailed usage." = "سجّل الدخول إلى منصة DeepSeek في Chrome لعرض الاستخدام التفصيلي.";
907+
"Select a DeepSeek Chrome profile in Settings." = "حدد ملف تعريف Chrome لـ DeepSeek في الإعدادات.";
908+
"DeepSeek this month token usage trend" = "اتجاه استخدام رموز DeepSeek لهذا الشهر";
909+
"Chrome profile" = "ملف تعريف Chrome";
910+
"Choose which signed-in DeepSeek Platform session supplies detailed usage." = "اختر جلسة DeepSeek Platform المسجّل دخولها التي توفّر تفاصيل الاستخدام.";
911+
"Select profile…" = "اختر ملفًا شخصيًا…";
905912
"cache-hit input" = "إدخال الضربات المؤقتة";
906913
"cache-miss input" = "إدخال ذاكرة تخزين مؤقت (CACHE-miss)";
907914
"output" = "الإنتاج";

Sources/CodexBar/Resources/ca.lproj/Localizable.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,13 @@
765765
"MiniMax 30 day token usage trend" = "Tendència d'ús de tokens de 30 dies de MiniMax";
766766
"Today cash" = "Efectiu d'avui";
767767
"DeepSeek 30 day token usage trend" = "Tendència d'ús de tokens de 30 dies de DeepSeek";
768+
"Detailed usage unavailable." = "L'ús detallat no està disponible.";
769+
"Sign in to DeepSeek Platform in Chrome for detailed usage." = "Inicia la sessió a DeepSeek Platform al Chrome per veure l'ús detallat.";
770+
"Select a DeepSeek Chrome profile in Settings." = "Selecciona un perfil de Chrome de DeepSeek a Configuració.";
771+
"DeepSeek this month token usage trend" = "Tendència d'ús de tokens de DeepSeek aquest mes";
772+
"Chrome profile" = "Perfil de Chrome";
773+
"Choose which signed-in DeepSeek Platform session supplies detailed usage." = "Tria quina sessió iniciada de DeepSeek Platform proporciona l'ús detallat.";
774+
"Select profile…" = "Selecciona un perfil…";
768775
"cache-hit input" = "entrada amb encert de memòria cau";
769776
"cache-miss input" = "entrada sense encert de memòria cau";
770777
"output" = "sortida";

0 commit comments

Comments
 (0)