fix(relay): handle dead connections and preserve usage on refresh failure
Use received frames for peer liveness and reject new requests after terminal relay failures. Keep authoritative usage snapshots separate from refresh errors, bound request lifetime, and guard concurrent refreshes and runtime switches. Add regression coverage for relay liveness, terminal rejections, quota failures, cancellation and request deadlines. Validated with 51 focused tests, workspace type-check and lint, web build and mobile assets.
This commit is contained in:
@@ -47,6 +47,7 @@ export const UsagePage: React.FC = () => {
|
||||
const isLoading = useQuotaStore((state) => state.isLoading);
|
||||
const lastUpdated = useQuotaStore((state) => state.lastUpdated);
|
||||
const error = useQuotaStore((state) => state.error);
|
||||
const refreshErrors = useQuotaStore((state) => state.refreshErrors);
|
||||
const dropdownProviderIds = useQuotaStore((state) => state.dropdownProviderIds);
|
||||
const setDropdownProviderIds = useQuotaStore((state) => state.setDropdownProviderIds);
|
||||
const selectedModels = useQuotaStore((state) => state.selectedModels);
|
||||
@@ -76,9 +77,14 @@ export const UsagePage: React.FC = () => {
|
||||
const providerMeta = QUOTA_PROVIDERS.find((provider) => provider.id === selectedProviderId);
|
||||
const providerName = providerMeta?.name ?? selectedProviderId ?? t('settings.usage.sidebar.title');
|
||||
const usage = selectedResult?.usage;
|
||||
const selectedProviderError = selectedResult?.configured && !selectedResult.ok
|
||||
? selectedResult.error
|
||||
: null;
|
||||
const refreshError = selectedProviderId ? refreshErrors[selectedProviderId] : undefined;
|
||||
const selectedProviderError = refreshError
|
||||
? usage
|
||||
? t('header.services.usageRefreshFailedStale', { error: refreshError })
|
||||
: refreshError
|
||||
: selectedResult?.configured && !selectedResult.ok
|
||||
? selectedResult.error
|
||||
: null;
|
||||
const showInDropdown = selectedProviderId ? dropdownProviderIds.includes(selectedProviderId) : false;
|
||||
const hasCredentialsForm = selectedProviderId === 'exe-dev' || selectedProviderId === 'ollama-cloud' || selectedProviderId === 'cursor';
|
||||
const handleDropdownToggle = React.useCallback((enabled: boolean) => {
|
||||
@@ -190,7 +196,7 @@ export const UsagePage: React.FC = () => {
|
||||
{(error || selectedProviderError) && (
|
||||
<div className="mb-8 rounded-lg border border-[var(--status-error-border)] bg-[var(--status-error-background)] px-4 py-3">
|
||||
<p className="typography-ui-label font-medium text-[var(--status-error)]">{t('settings.usage.page.state.refreshFailedTitle')}</p>
|
||||
<p className="typography-meta text-[var(--status-error)]/80 mt-1">{error ?? selectedProviderError}</p>
|
||||
<p className="typography-meta text-[var(--status-error)]/80 mt-1">{selectedProviderError ?? error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export const UsageSidebar: React.FC<UsageSidebarProps> = ({ onItemSelect }) => {
|
||||
const percent = getUsagePercent(result?.usage);
|
||||
const tone = resolveUsageTone(percent);
|
||||
const isSelected = provider.id === selectedProviderId;
|
||||
const configured = result?.configured ?? false;
|
||||
const configured = result?.configured;
|
||||
|
||||
const statusStyle = !configured
|
||||
? { backgroundColor: 'var(--surface-muted-foreground)', opacity: 0.4 }
|
||||
@@ -129,7 +129,7 @@ export const UsageSidebar: React.FC<UsageSidebarProps> = ({ onItemSelect }) => {
|
||||
<span className="typography-ui-label font-normal truncate flex-1 min-w-0 text-foreground">
|
||||
{provider.name}
|
||||
</span>
|
||||
{!configured && (
|
||||
{configured === false && (
|
||||
<span className="typography-micro text-muted-foreground/60 flex-shrink-0">{t('settings.usage.sidebar.status.notSet')}</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user