3D 提交编排整段落在错误边界内

- src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts:把取占位框、算结果标题与构造提交计划一并移进 try,注入的回调或老快照缺字段导致的抛错不再漏成未捕获 rejection,而是照常写回失败态与提示
- src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx:补「计划构造阶段抛错」的用例(缺 prompt 的老快照)
This commit is contained in:
2026-09-24 17:30:53 +08:00
parent 2350c56f2f
commit 5ba7a993de
2 changed files with 51 additions and 32 deletions
@@ -276,6 +276,23 @@ describe('useModel3dGenerationTask', () => {
expect(result.current.dialog.composerOpen).toBe(true);
});
it('构造提交计划时抛的错也落到失败态,而不是漏成未捕获的 rejection', async () => {
// 老快照缺字段:resolveModel3dResultTitle 会读 dialog.prompt.trim(),在计划构造前就抛。
const { result } = renderTask({
dialog: createDialog({ prompt: undefined }),
});
await act(async () => {
await result.current.submitModel3dGeneration(
createDialog({ prompt: undefined }),
);
});
expect(result.current.dialog.status).toBe('failed');
expect(result.current.dialog.composerOpen).toBe(true);
expect(submitModel3dTextToModelRequestMock).not.toHaveBeenCalled();
});
it('定价段缺失时不发请求,只把不可提交的原因写回面板', async () => {
applyEditorGenerationPricingConfig({
models: EDITOR_MODEL_MUD_POINT_CONFIG,
@@ -74,41 +74,43 @@ export function useModel3dGenerationTask({
if (!dialogId || !isModel3dGenerationMode(dialog.mode)) {
return;
}
const placeholder = getGeneratingDialogPlaceholder(dialog);
const canvasCompletion =
projectId && placeholder
? {
dialogId,
title: resolveModel3dResultTitle(dialog),
placeholder,
}
: null;
const planResult = buildModel3dSubmissionPlan({
dialog,
projectId,
canvasCompletion,
assetFolderId,
assetLabel: resolveModel3dResultTitle(dialog),
pricing: readEditorModel3dPricingConfig(),
});
if (!planResult.ok) {
// 整个提交编排都落在 try 里:取占位框、算标题、构造提交计划同样可能抛
// (注入的回调、老快照缺字段),漏出去就成了一次没有用户提示的未捕获 rejection。
try {
const placeholder = getGeneratingDialogPlaceholder(dialog);
const canvasCompletion =
projectId && placeholder
? {
dialogId,
title: resolveModel3dResultTitle(dialog),
placeholder,
}
: null;
const planResult = buildModel3dSubmissionPlan({
dialog,
projectId,
canvasCompletion,
assetFolderId,
assetLabel: resolveModel3dResultTitle(dialog),
pricing: readEditorModel3dPricingConfig(),
});
if (!planResult.ok) {
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
...currentDialog,
status: 'failed',
composerOpen: true,
errorMessage: planResult.message,
}));
return;
}
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
...currentDialog,
status: 'failed',
composerOpen: true,
errorMessage: planResult.message,
// 兜底铸的代次要写回状态:否则老快照每次提交都换幂等键,去重保护失效。
model3dAttemptNonce: planResult.plan.attemptNonce,
status: 'generating',
composerOpen: false,
errorMessage: undefined,
}));
return;
}
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
...currentDialog,
// 兜底铸的代次要写回状态:否则老快照每次提交都换幂等键,去重保护失效。
model3dAttemptNonce: planResult.plan.attemptNonce,
status: 'generating',
composerOpen: false,
errorMessage: undefined,
}));
try {
const plan = planResult.plan;
// 判别联合在这里收窄:文生与图生各走自己的客户端函数,端点与提交体的配对由类型保证。
const response =