fix(team): surface provider retries in reply UI
This commit is contained in:
parent
8ebde439a8
commit
7f737f985b
4 changed files with 50 additions and 12 deletions
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@renderer/utils/memberHelpers';
|
||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { ShieldQuestion, Users } from 'lucide-react';
|
||||
import { Loader2, ShieldQuestion, Users } from 'lucide-react';
|
||||
|
||||
import type { ResolvedTeamMember } from '@shared/types';
|
||||
|
||||
|
|
@ -83,6 +83,7 @@ export const PendingRepliesBlock = ({
|
|||
);
|
||||
const advisoryLabel = getMemberRuntimeAdvisoryLabel(member.runtimeAdvisory);
|
||||
const advisoryTitle = getMemberRuntimeAdvisoryTitle(member.runtimeAdvisory);
|
||||
const isRetrying = advisoryLabel !== null;
|
||||
|
||||
return (
|
||||
<article
|
||||
|
|
@ -103,8 +104,12 @@ export const PendingRepliesBlock = ({
|
|||
loading="lazy"
|
||||
/>
|
||||
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
|
||||
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-70" />
|
||||
<span className="relative inline-flex size-full rounded-full bg-emerald-500" />
|
||||
<span
|
||||
className={`absolute inline-flex size-full animate-ping rounded-full opacity-70 ${isRetrying ? 'bg-amber-400' : 'bg-emerald-400'}`}
|
||||
/>
|
||||
<span
|
||||
className={`relative inline-flex size-full rounded-full ${isRetrying ? 'bg-amber-500' : 'bg-emerald-500'}`}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
{onMemberClick ? (
|
||||
|
|
@ -139,12 +144,15 @@ export const PendingRepliesBlock = ({
|
|||
</span>
|
||||
) : null}
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate text-[10px]"
|
||||
style={{ color: CARD_ICON_MUTED }}
|
||||
className={`min-w-0 flex-1 truncate text-[10px] ${isRetrying ? 'text-amber-300' : ''}`}
|
||||
style={isRetrying ? undefined : { color: CARD_ICON_MUTED }}
|
||||
title={advisoryTitle ?? 'Message sent, awaiting reply'}
|
||||
>
|
||||
{advisoryLabel ?? 'awaiting reply'}
|
||||
</span>
|
||||
{isRetrying ? (
|
||||
<Loader2 className="size-3 shrink-0 animate-spin text-amber-400" />
|
||||
) : null}
|
||||
<span className="shrink-0 text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||
{since}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -179,11 +179,11 @@ export const MemberCard = ({
|
|||
{!activityTask && isAwaitingReply ? (
|
||||
<>
|
||||
<Loader2
|
||||
className="size-3 shrink-0 animate-spin"
|
||||
style={{ color: colors.border }}
|
||||
className={`size-3 shrink-0 animate-spin ${runtimeAdvisoryLabel ? 'text-amber-400' : ''}`}
|
||||
style={runtimeAdvisoryLabel ? undefined : { color: colors.border }}
|
||||
/>
|
||||
<span
|
||||
className="shrink-0 text-[10px] text-[var(--color-text-muted)]"
|
||||
className={`shrink-0 text-[10px] ${runtimeAdvisoryLabel ? 'text-amber-300' : 'text-[var(--color-text-muted)]'}`}
|
||||
title={runtimeAdvisoryTitle ?? 'Message sent, awaiting reply'}
|
||||
>
|
||||
{runtimeAdvisoryLabel ?? 'awaiting reply'}
|
||||
|
|
|
|||
|
|
@ -214,13 +214,13 @@ export function getMemberRuntimeAdvisoryLabel(
|
|||
}
|
||||
const retryUntilMs = Date.parse(advisory.retryUntil);
|
||||
if (!Number.isFinite(retryUntilMs)) {
|
||||
return 'SDK retrying';
|
||||
return 'retrying now';
|
||||
}
|
||||
const remainingMs = retryUntilMs - nowMs;
|
||||
if (remainingMs <= 0) {
|
||||
return 'SDK retrying';
|
||||
return 'retrying now';
|
||||
}
|
||||
return `SDK retrying · ${formatRetryCountdown(remainingMs)}`;
|
||||
return `retrying now · ${formatRetryCountdown(remainingMs)}`;
|
||||
}
|
||||
|
||||
export function getMemberRuntimeAdvisoryTitle(
|
||||
|
|
@ -229,7 +229,10 @@ export function getMemberRuntimeAdvisoryTitle(
|
|||
if (!advisory || advisory.kind !== 'sdk_retrying') {
|
||||
return undefined;
|
||||
}
|
||||
return advisory.message?.trim() || 'The SDK is retrying after a provider error.';
|
||||
return (
|
||||
advisory.message?.trim() ||
|
||||
'The SDK is retrying this request after a provider or backend error.'
|
||||
);
|
||||
}
|
||||
|
||||
export const TASK_STATUS_STYLES: Record<TeamTaskStatus, { bg: string; text: string }> = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
getSpawnAwareDotClass,
|
||||
getSpawnAwarePresenceLabel,
|
||||
getMemberRuntimeAdvisoryLabel,
|
||||
getMemberRuntimeAdvisoryTitle,
|
||||
} from '@renderer/utils/memberHelpers';
|
||||
|
||||
import type { ResolvedTeamMember } from '@shared/types';
|
||||
|
|
@ -59,4 +61,29 @@ describe('memberHelpers spawn-aware presence', () => {
|
|||
)
|
||||
).toBe('starting');
|
||||
});
|
||||
|
||||
it('renders unified retry advisory labels for provider retries', () => {
|
||||
expect(
|
||||
getMemberRuntimeAdvisoryLabel(
|
||||
{
|
||||
kind: 'sdk_retrying',
|
||||
observedAt: '2026-04-07T09:00:00.000Z',
|
||||
retryUntil: '2026-04-07T09:00:45.000Z',
|
||||
retryDelayMs: 45_000,
|
||||
message: 'Gemini cli backend error: capacity exceeded.',
|
||||
},
|
||||
Date.parse('2026-04-07T09:00:00.000Z')
|
||||
)
|
||||
).toBe('retrying now · 45s');
|
||||
|
||||
expect(
|
||||
getMemberRuntimeAdvisoryTitle({
|
||||
kind: 'sdk_retrying',
|
||||
observedAt: '2026-04-07T09:00:00.000Z',
|
||||
retryUntil: '2026-04-07T09:00:45.000Z',
|
||||
retryDelayMs: 45_000,
|
||||
message: 'Gemini cli backend error: capacity exceeded.',
|
||||
})
|
||||
).toContain('capacity exceeded');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue