3D 失败态下主按钮也开新一代,不再重现同一次失败

- Model3dGenerationModal 主按钮与「重试」共用失败态重置后的 dialog(refreshModel3dAttemptNonce),失败态点主按钮不再沿用旧幂等键
- 新增用例:失败态主按钮提交的代次必须变化、状态回到 idle,且按钮保持可点
This commit is contained in:
2026-09-23 18:51:15 +08:00
parent df6694c4a9
commit 5ebc6df6ae
2 changed files with 31 additions and 2 deletions
@@ -298,4 +298,28 @@ describe('Model3dGenerationModal', () => {
);
expect(onSubmit.mock.calls[0][0].model3dAttemptNonce).toBeTruthy();
});
it('失败态下主按钮也开新一代,不会重现同一次失败', () => {
const onSubmit = vi.fn();
const failedDialog = createDialog('model3d-text-to-model', {
prompt: '一把木椅',
status: 'failed',
errorMessage: '3D 模型生成失败,请稍后重试。',
});
render(
<Model3dModalHarness initialDialog={failedDialog} onSubmit={onSubmit} />,
);
// 主按钮在失败态仍是「再生成一次」的自然入口,可以点;但不能沿用旧代次,
// 否则幂等键不变、服务端只会把原来那个终态 operation 还回来。
expect((submitButton() as HTMLButtonElement).disabled).toBe(false);
fireEvent.click(submitButton());
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0][0].model3dAttemptNonce).not.toBe(
failedDialog.model3dAttemptNonce,
);
expect(onSubmit.mock.calls[0][0].model3dAttemptNonce).toBeTruthy();
expect(onSubmit.mock.calls[0][0].status).toBe('idle');
});
});
@@ -71,6 +71,11 @@ export function Model3dGenerationModal({
hasRequiredInput &&
!isGenerating &&
!hasPendingImageReferenceUploads;
// 幂等键 = 代次 + 请求内容指纹,同键同请求只会拿回原来那个终态 operation。
// 所以失败态下的任何提交(主按钮或「重试」)都要开新一代,否则点下去只是重现同一次失败;
// 参数一改本来就会换代号次,这里补的是「不改参数直接再点一次」这条路。
const submitDialog =
dialog.status === 'failed' ? refreshModel3dAttemptNonce(dialog) : dialog;
useImageCanvasFloatingOptionDismiss({
isOpen: isImageMode && isGenerationReferenceMenuOpen,
@@ -114,7 +119,7 @@ export function Model3dGenerationModal({
className="image-canvas-editor__generation-submit"
disabled={!canSubmit}
aria-label={MODEL3D_GENERATION_SUBMIT_LABEL}
onClick={() => onSubmit(dialog)}
onClick={() => onSubmit(submitDialog)}
>
{isGenerating ? (
'生成中'
@@ -135,7 +140,7 @@ export function Model3dGenerationModal({
size="xs"
shape="pill"
aria-label="重试生成 3D 模型"
onClick={() => onSubmit(refreshModel3dAttemptNonce(dialog))}
onClick={() => onSubmit(submitDialog)}
>
</PlatformActionButton>