diff --git a/frontend/src/components/chat/GeneratedImages.test.tsx b/frontend/src/components/chat/GeneratedImages.test.tsx index ef6d2e06..318f6ad5 100644 --- a/frontend/src/components/chat/GeneratedImages.test.tsx +++ b/frontend/src/components/chat/GeneratedImages.test.tsx @@ -132,6 +132,7 @@ describe('GeneratedImages', () => { const card = screen.getByTestId('generated-image-pending'); expect(card).toHaveStyle({ width: `${imageDisplayWidth(2, 3)}px` }); expect(screen.getByRole('status')).toHaveTextContent('正在生成图片'); + expect(screen.getByText('正在生成图片')).toHaveClass('sr-only'); expect(screen.getByText('12 秒')).toBeInTheDocument(); act(() => { @@ -179,4 +180,17 @@ describe('collectPendingImageGenerations', () => { ]); expect(collectPendingImageGenerations(null, [])).toEqual([]); }); + + it('drops a failed card once a later generation replaces it', () => { + const failed = { status: 'failed' as const, resultSummary: { kind: 'generated_image', truncated: false, error_code: 'no_image_returned' } as never }; + const retrying = runWith([call(failed), call({ toolCallId: 'call-2' })]); + expect(collectPendingImageGenerations(retrying, [])).toMatchObject([{ toolCallId: 'call-2', status: 'running' }]); + + const retried = runWith([call(failed), call({ + toolCallId: 'call-2', + status: 'success', + resultSummary: { kind: 'generated_image', truncated: false, file_id: 'file-1' } as never, + })]); + expect(collectPendingImageGenerations(retried, [block])).toEqual([]); + }); }); diff --git a/frontend/src/components/chat/GeneratedImages.tsx b/frontend/src/components/chat/GeneratedImages.tsx index 92699df6..34856d77 100644 --- a/frontend/src/components/chat/GeneratedImages.tsx +++ b/frontend/src/components/chat/GeneratedImages.tsx @@ -94,7 +94,7 @@ function PendingImageCard({ pending }: { pending: PendingImageGeneration }) { {label} diff --git a/frontend/src/components/chat/generatedImageModel.ts b/frontend/src/components/chat/generatedImageModel.ts index bf72f06f..04fbc791 100644 --- a/frontend/src/components/chat/generatedImageModel.ts +++ b/frontend/src/components/chat/generatedImageModel.ts @@ -57,33 +57,34 @@ export function collectPendingImageGenerations( ): PendingImageGeneration[] { if (!run) return []; const renderedFileIds = new Set(blocks.map(block => block.file_id)); + const calls = run.steps.flatMap(step => step.toolCalls.filter(call => call.toolName === GENERATE_IMAGE_TOOL_NAME)); const pending: PendingImageGeneration[] = []; - for (const step of run.steps) { - for (const call of step.toolCalls) { - if (call.toolName !== GENERATE_IMAGE_TOOL_NAME) continue; - const summary = call.resultSummary as (Record | undefined); - const fileId = typeof summary?.file_id === 'string' ? summary.file_id : null; - let status: PendingImageStatus; - if (call.status === 'running') { - status = 'running'; - } else if (call.status === 'success') { - if (!fileId || renderedFileIds.has(fileId)) continue; - status = 'running'; - } else if (call.status === 'interrupted') { - status = 'interrupted'; - } else { - status = 'failed'; - } - const aspectRatio = typeof call.arguments?.aspect_ratio === 'string' ? call.arguments.aspect_ratio : '1:1'; - pending.push({ - toolCallId: call.toolCallId, - status, - aspectRatio, - startedAt: call.startedAt, - completedAt: call.completedAt, - errorCode: typeof summary?.error_code === 'string' ? summary.error_code : null, - }); + calls.forEach((call, index) => { + const summary = call.resultSummary as (Record | undefined); + const fileId = typeof summary?.file_id === 'string' ? summary.file_id : null; + let status: PendingImageStatus; + if (call.status === 'running') { + status = 'running'; + } else if (call.status === 'success') { + if (!fileId || renderedFileIds.has(fileId)) return; + status = 'running'; + } else if (index < calls.length - 1) { + // 失败或中断后又发起了生图(重试),由后一张卡片接替,不再保留失败卡片。 + return; + } else if (call.status === 'interrupted') { + status = 'interrupted'; + } else { + status = 'failed'; } - } + const aspectRatio = typeof call.arguments?.aspect_ratio === 'string' ? call.arguments.aspect_ratio : '1:1'; + pending.push({ + toolCallId: call.toolCallId, + status, + aspectRatio, + startedAt: call.startedAt, + completedAt: call.completedAt, + errorCode: typeof summary?.error_code === 'string' ? summary.error_code : null, + }); + }); return pending; }