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';
|
} from '@renderer/utils/memberHelpers';
|
||||||
import { nameColorSet } from '@renderer/utils/projectColor';
|
import { nameColorSet } from '@renderer/utils/projectColor';
|
||||||
import { formatDistanceToNowStrict } from 'date-fns';
|
import { formatDistanceToNowStrict } from 'date-fns';
|
||||||
import { ShieldQuestion, Users } from 'lucide-react';
|
import { Loader2, ShieldQuestion, Users } from 'lucide-react';
|
||||||
|
|
||||||
import type { ResolvedTeamMember } from '@shared/types';
|
import type { ResolvedTeamMember } from '@shared/types';
|
||||||
|
|
||||||
|
|
@ -83,6 +83,7 @@ export const PendingRepliesBlock = ({
|
||||||
);
|
);
|
||||||
const advisoryLabel = getMemberRuntimeAdvisoryLabel(member.runtimeAdvisory);
|
const advisoryLabel = getMemberRuntimeAdvisoryLabel(member.runtimeAdvisory);
|
||||||
const advisoryTitle = getMemberRuntimeAdvisoryTitle(member.runtimeAdvisory);
|
const advisoryTitle = getMemberRuntimeAdvisoryTitle(member.runtimeAdvisory);
|
||||||
|
const isRetrying = advisoryLabel !== null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
|
|
@ -103,8 +104,12 @@ export const PendingRepliesBlock = ({
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<span className="absolute -bottom-0.5 -right-0.5 flex size-2.5">
|
<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
|
||||||
<span className="relative inline-flex size-full rounded-full bg-emerald-500" />
|
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>
|
||||||
</span>
|
</span>
|
||||||
{onMemberClick ? (
|
{onMemberClick ? (
|
||||||
|
|
@ -139,12 +144,15 @@ export const PendingRepliesBlock = ({
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span
|
<span
|
||||||
className="min-w-0 flex-1 truncate text-[10px]"
|
className={`min-w-0 flex-1 truncate text-[10px] ${isRetrying ? 'text-amber-300' : ''}`}
|
||||||
style={{ color: CARD_ICON_MUTED }}
|
style={isRetrying ? undefined : { color: CARD_ICON_MUTED }}
|
||||||
title={advisoryTitle ?? 'Message sent, awaiting reply'}
|
title={advisoryTitle ?? 'Message sent, awaiting reply'}
|
||||||
>
|
>
|
||||||
{advisoryLabel ?? 'awaiting reply'}
|
{advisoryLabel ?? 'awaiting reply'}
|
||||||
</span>
|
</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 }}>
|
<span className="shrink-0 text-[10px]" style={{ color: CARD_ICON_MUTED }}>
|
||||||
{since}
|
{since}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -179,11 +179,11 @@ export const MemberCard = ({
|
||||||
{!activityTask && isAwaitingReply ? (
|
{!activityTask && isAwaitingReply ? (
|
||||||
<>
|
<>
|
||||||
<Loader2
|
<Loader2
|
||||||
className="size-3 shrink-0 animate-spin"
|
className={`size-3 shrink-0 animate-spin ${runtimeAdvisoryLabel ? 'text-amber-400' : ''}`}
|
||||||
style={{ color: colors.border }}
|
style={runtimeAdvisoryLabel ? undefined : { color: colors.border }}
|
||||||
/>
|
/>
|
||||||
<span
|
<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'}
|
title={runtimeAdvisoryTitle ?? 'Message sent, awaiting reply'}
|
||||||
>
|
>
|
||||||
{runtimeAdvisoryLabel ?? 'awaiting reply'}
|
{runtimeAdvisoryLabel ?? 'awaiting reply'}
|
||||||
|
|
|
||||||
|
|
@ -214,13 +214,13 @@ export function getMemberRuntimeAdvisoryLabel(
|
||||||
}
|
}
|
||||||
const retryUntilMs = Date.parse(advisory.retryUntil);
|
const retryUntilMs = Date.parse(advisory.retryUntil);
|
||||||
if (!Number.isFinite(retryUntilMs)) {
|
if (!Number.isFinite(retryUntilMs)) {
|
||||||
return 'SDK retrying';
|
return 'retrying now';
|
||||||
}
|
}
|
||||||
const remainingMs = retryUntilMs - nowMs;
|
const remainingMs = retryUntilMs - nowMs;
|
||||||
if (remainingMs <= 0) {
|
if (remainingMs <= 0) {
|
||||||
return 'SDK retrying';
|
return 'retrying now';
|
||||||
}
|
}
|
||||||
return `SDK retrying · ${formatRetryCountdown(remainingMs)}`;
|
return `retrying now · ${formatRetryCountdown(remainingMs)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMemberRuntimeAdvisoryTitle(
|
export function getMemberRuntimeAdvisoryTitle(
|
||||||
|
|
@ -229,7 +229,10 @@ export function getMemberRuntimeAdvisoryTitle(
|
||||||
if (!advisory || advisory.kind !== 'sdk_retrying') {
|
if (!advisory || advisory.kind !== 'sdk_retrying') {
|
||||||
return undefined;
|
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 }> = {
|
export const TASK_STATUS_STYLES: Record<TeamTaskStatus, { bg: string; text: string }> = {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import {
|
import {
|
||||||
getSpawnAwareDotClass,
|
getSpawnAwareDotClass,
|
||||||
getSpawnAwarePresenceLabel,
|
getSpawnAwarePresenceLabel,
|
||||||
|
getMemberRuntimeAdvisoryLabel,
|
||||||
|
getMemberRuntimeAdvisoryTitle,
|
||||||
} from '@renderer/utils/memberHelpers';
|
} from '@renderer/utils/memberHelpers';
|
||||||
|
|
||||||
import type { ResolvedTeamMember } from '@shared/types';
|
import type { ResolvedTeamMember } from '@shared/types';
|
||||||
|
|
@ -59,4 +61,29 @@ describe('memberHelpers spawn-aware presence', () => {
|
||||||
)
|
)
|
||||||
).toBe('starting');
|
).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