From fc24ec933b4744b54be0a836abe91ef77ddb2dd4 Mon Sep 17 00:00:00 2001 From: null Date: Sun, 24 May 2026 20:49:45 -0500 Subject: [PATCH] navbar is now explicitly local-first --- backend/app/services/provider_usage.py | 40 +++++++++++++++++-- .../src/components/git/ForgejoHeatmap.tsx | 18 ++++----- .../organisms/ProviderNavbarStatus.tsx | 20 ++++++++-- .../src/lib/provider-credential-selection.ts | 10 ++++- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/backend/app/services/provider_usage.py b/backend/app/services/provider_usage.py index fe52ce0..8e94a8e 100644 --- a/backend/app/services/provider_usage.py +++ b/backend/app/services/provider_usage.py @@ -64,6 +64,7 @@ CACHE_TTL_SECONDS = 60 # next auto-poll only fires after the window has cleared. Manual refreshes # (force_refresh=True) always bypass this TTL. CACHE_TTL_FAILURE_SECONDS = 60 +STALE_SUBSCRIPTION_TTL_SECONDS = 10 * 60 REQUEST_TIMEOUT = 8.0 # seconds @@ -924,6 +925,7 @@ async def _fetch_codex_subscription(session_key: str) -> tuple[list[Subscription # --------------------------------------------------------------------------- _cache: dict[str, tuple[datetime, ProviderUsageLive, int]] = {} +_last_subscription_cache: dict[str, tuple[datetime, ProviderUsageLive]] = {} # Tracks in-progress fetches so concurrent requests share one result instead # of each racing to hit the provider API (which triggers 429 cascades). _inflight: dict[str, asyncio.Future[ProviderUsageLive]] = {} @@ -944,6 +946,22 @@ def _set_cached(cache_key: str, result: ProviderUsageLive, ttl: int = CACHE_TTL_ _cache[cache_key] = (utcnow(), result, ttl) +def _get_last_subscription(cache_key: str) -> ProviderUsageLive | None: + entry = _last_subscription_cache.get(cache_key) + if entry is None: + return None + cached_at, result = entry + if (utcnow() - cached_at).total_seconds() > STALE_SUBSCRIPTION_TTL_SECONDS: + del _last_subscription_cache[cache_key] + return None + return result + + +def _remember_subscription(cache_key: str, result: ProviderUsageLive) -> None: + if result.subscription_windows: + _last_subscription_cache[cache_key] = (utcnow(), result) + + def _secret_fingerprint(value: str | None) -> str: if not value: return "none" @@ -975,6 +993,9 @@ def _usage_cache_key( return f"anthropic:api:{normalized_base_url}:{_secret_fingerprint(api_key)}" if normalized_provider in ("openai", "codex"): + local_codex = _read_codex_local_token() + if local_codex: + return f"{normalized_provider}:local-oauth:{_secret_fingerprint(local_codex)}" if api_key: return ( f"{normalized_provider}:api:" @@ -982,9 +1003,6 @@ def _usage_cache_key( ) if session_key: return f"{normalized_provider}:session:{_secret_fingerprint(session_key)}" - local_codex = _read_codex_local_token() - if local_codex: - return f"{normalized_provider}:local-oauth:{_secret_fingerprint(local_codex)}" if normalized_provider == "ollama": return f"ollama:{normalized_base_url}:{_secret_fingerprint(api_key)}" @@ -1108,6 +1126,22 @@ async def _do_fetch_provider_usage( ) result.account_key = account_key + if subscription_attempted and not result.subscription_windows: + stale = _get_last_subscription(cache_key) + if stale is not None: + stale.checked_at = utcnow() + stale.account_key = account_key + stale.error = None + _set_cached(cache_key, stale, ttl=CACHE_TTL_FAILURE_SECONDS) + logger.info( + "provider_usage.subscription.stale_used provider=%s account=%s error=%s", + provider, + account_key, + result.error, + ) + return stale + + _remember_subscription(cache_key, result) # Use a short TTL when subscription windows were expected but came back empty # (e.g. a transient 429 at startup). This avoids persisting a 60s stale result # while still preventing the thundering-herd that occurs with no caching at all. diff --git a/frontend/src/components/git/ForgejoHeatmap.tsx b/frontend/src/components/git/ForgejoHeatmap.tsx index 837e41b..110f43d 100644 --- a/frontend/src/components/git/ForgejoHeatmap.tsx +++ b/frontend/src/components/git/ForgejoHeatmap.tsx @@ -248,7 +248,7 @@ function LineChart({ days, range, onRangeChange, lastPush }: {
- Git Activity + Git Commits {displayedCount.toLocaleString()} @@ -261,7 +261,7 @@ function LineChart({ days, range, onRangeChange, lastPush }: { value={range} onValueChange={onRangeChange} accent="violet" - ariaLabel="Select Git Activity range" + ariaLabel="Select Git Commits range" />
@@ -315,7 +315,7 @@ function LineChart({ days, range, onRangeChange, lastPush }: { onBlur={() => setHoveredPoint(null)} style={{fill:"transparent", outline:"none"}} > - {p.date}{p.count > 0 ? `: ${p.count} event${p.count!==1?"s":""}` : ": no activity"} + {p.date}{p.count > 0 ? `: ${p.count} commit${p.count!==1?"s":""}` : ": no commits"} ))} @@ -466,7 +466,7 @@ function HeatmapGrid({ days, range, onRangeChange }: {
- Activity Heatmap + Commit Heatmap {displayedCount.toLocaleString()} @@ -479,12 +479,12 @@ function HeatmapGrid({ days, range, onRangeChange }: { value={range} onValueChange={onRangeChange} accent="green" - ariaLabel="Select Activity Heatmap range" + ariaLabel="Select Commit Heatmap range" />
{/* SVG */} - + {heatmap.mode === "strip" ? ( <> {heatmap.daily.map((day, i) => ( @@ -507,7 +507,7 @@ function HeatmapGrid({ days, range, onRangeChange }: { outline: "none", }} > - {day.date}{day.count>0?`: ${day.count} event${day.count!==1?"s":""}` : ": no activity"} + {day.date}{day.count>0?`: ${day.count} commit${day.count!==1?"s":""}` : ": no commits"} {(range === "7d" || i % 5 === 0 || i === heatmap.daily.length - 1) && ( - {cell.date}{cell.count>0?`: ${cell.count} event${cell.count!==1?"s":""}` : ": no activity"} + {cell.date}{cell.count>0?`: ${cell.count} commit${cell.count!==1?"s":""}` : ": no commits"} )) )} @@ -566,7 +566,7 @@ function HeatmapGrid({ days, range, onRangeChange }: { {heatmap.totalEvents.toLocaleString()} - contributions across all tracked repositories in the last {RANGE_SUMMARY[range]} + commits across all tracked repositories in the last {RANGE_SUMMARY[range]}

diff --git a/frontend/src/components/organisms/ProviderNavbarStatus.tsx b/frontend/src/components/organisms/ProviderNavbarStatus.tsx index 7f40d17..18e3c12 100644 --- a/frontend/src/components/organisms/ProviderNavbarStatus.tsx +++ b/frontend/src/components/organisms/ProviderNavbarStatus.tsx @@ -252,7 +252,7 @@ export function ProviderNavbarStatus() { >({}); const [isUsageLoading, setIsUsageLoading] = useState(false); const [lastFetchedAt, setLastFetchedAt] = useState(null); - const [now, setNow] = useState(Date.now()); + const [now, setNow] = useState(0); useEffect(() => { const id = setInterval(() => setNow(Date.now()), 1000); @@ -317,7 +317,21 @@ export function ProviderNavbarStatus() { }), ); if (!cancelled) { - setUsageByCredentialId(Object.fromEntries(pairs)); + setUsageByCredentialId((previous) => { + const next = { ...previous }; + for (const [credentialId, usage] of pairs) { + const previousUsage = previous[credentialId]; + const nextWindows = usage?.subscription_windows ?? []; + const previousWindows = previousUsage?.subscription_windows ?? []; + next[credentialId] = + usage && + nextWindows.length === 0 && + previousWindows.length > 0 + ? previousUsage + : usage; + } + return next; + }); setLastFetchedAt(new Date()); setIsUsageLoading(false); } @@ -366,7 +380,7 @@ export function ProviderNavbarStatus() { <> - Updated {fmtElapsed(lastFetchedAt, now)} + Updated {fmtElapsed(lastFetchedAt, now || lastFetchedAt.getTime())} )} diff --git a/frontend/src/lib/provider-credential-selection.ts b/frontend/src/lib/provider-credential-selection.ts index fabd53f..df8c480 100644 --- a/frontend/src/lib/provider-credential-selection.ts +++ b/frontend/src/lib/provider-credential-selection.ts @@ -4,13 +4,19 @@ export function credentialIsUsable(cred: ProviderCredentialRead): boolean { return cred.active && (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)); } +function credentialIsLocal(cred: ProviderCredentialRead): boolean { + return ( + cred.account_key === "default" || + cred.display_name.toLowerCase().includes("local") + ); +} + function providerCredentialScore(cred: ProviderCredentialRead): number { let score = 0; if (cred.active) score += 100; + if (credentialIsLocal(cred)) score += 1000; if (cred.has_session_key) score += 50; - if (cred.account_key === "default") score += 40; - if (cred.display_name.toLowerCase().includes("local")) score += 30; if (cred.has_api_key) score += 10; if (cred.base_url) score += 5;