fix(projects): 短剧项目 - 修复同名创建重试 (#691)
Co-authored-by: tanzui <305375675@.com>
This commit is contained in:
11
web/src/lib/project-name-conflict.ts
Normal file
11
web/src/lib/project-name-conflict.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ApiError } from "@/services/api/request";
|
||||
|
||||
const maximumProjectNameRetries = 5;
|
||||
|
||||
/** 只重试后端明确识别的项目重名,不依赖可能被统一错误处理改写的数据库文案。 */
|
||||
export function shouldRetryProjectNameConflict(error: unknown, attempt: number) {
|
||||
return error instanceof ApiError &&
|
||||
error.status === 409 &&
|
||||
error.reason === "project_name_conflict" &&
|
||||
attempt < maximumProjectNameRetries;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { resourceFileUrl } from "@/services/api/resources";
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
import { createStyleProfileSnapshot, parseStyleProfile, serializeStyleProfile } from "@/lib/canvas/style-profile";
|
||||
import { projectSummaryCompletion, projectSummaryStage } from "@/lib/project-workbench";
|
||||
import { shouldRetryProjectNameConflict } from "@/lib/project-name-conflict";
|
||||
import { settingsPath } from "@/lib/settings-navigation";
|
||||
import { PromptTemplateOperation, parseGeneratedStory, promptTemplateTaskPlaceholder, shortDramaOutlineVariables } from "@/lib/prompts";
|
||||
import { runBackendGenerationTask } from "@/services/api/generation-task";
|
||||
@@ -339,9 +340,7 @@ async function createUniqueProjectName(story: string, selectedStyle: CanvasStyle
|
||||
try {
|
||||
return await createProject(buildInput(attempt === 0 ? base : `${base}(${attempt + 1})`));
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "";
|
||||
const uniqueConflict = message.includes("UNIQUE") || message.includes("projects.user_id") || message.includes("projects.name");
|
||||
if (!uniqueConflict || attempt >= 5) throw error;
|
||||
if (!shouldRetryProjectNameConflict(error, attempt)) throw error;
|
||||
attempt += 1;
|
||||
}
|
||||
}
|
||||
|
||||
41
web/test/project-name-conflict.test.ts
Normal file
41
web/test/project-name-conflict.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { ApiError } from "../src/services/api/request";
|
||||
import { shouldRetryProjectNameConflict } from "../src/lib/project-name-conflict";
|
||||
|
||||
describe("project name conflict retries", () => {
|
||||
test("retries only the structured project-name conflict", () => {
|
||||
const projectNameConflict = new ApiError("文案可能变化", {
|
||||
status: 409,
|
||||
reason: "project_name_conflict",
|
||||
});
|
||||
|
||||
expect(shouldRetryProjectNameConflict(projectNameConflict, 0)).toBe(true);
|
||||
expect(shouldRetryProjectNameConflict(new ApiError("项目名称已存在", {
|
||||
status: 409,
|
||||
reason: "conflict",
|
||||
}), 0)).toBe(false);
|
||||
expect(shouldRetryProjectNameConflict(new Error("项目名称已存在"), 0)).toBe(false);
|
||||
});
|
||||
|
||||
test("stops after the configured number of name retries", () => {
|
||||
const projectNameConflict = new ApiError("项目名称已存在", {
|
||||
status: 409,
|
||||
reason: "project_name_conflict",
|
||||
});
|
||||
|
||||
expect(shouldRetryProjectNameConflict(projectNameConflict, 4)).toBe(true);
|
||||
expect(shouldRetryProjectNameConflict(projectNameConflict, 5)).toBe(false);
|
||||
});
|
||||
|
||||
test("错误文案、非 409 状态和缺少专用原因的错误不能触发重试", () => {
|
||||
for (const error of [
|
||||
new ApiError("项目名称已存在", { status: 500, reason: "project_name_conflict" }),
|
||||
new ApiError("UNIQUE constraint failed: projects.user_id, projects.name", { status: 409 }),
|
||||
new Error("UNIQUE constraint failed: projects.user_id, projects.name"),
|
||||
{ status: 409, reason: "project_name_conflict" },
|
||||
]) {
|
||||
expect(shouldRetryProjectNameConflict(error, 0)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user