From 7534bdf9b7a29f8ed7168caa47e62f4b012495f4 Mon Sep 17 00:00:00 2001 From: zizi Date: Wed, 17 Jun 2026 04:05:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(create):=20generic=20=E6=A1=A5=E6=8E=A5?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=80=E5=8F=A5=E8=AF=9D=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E6=97=A0=E9=9C=80=E9=80=89=E6=A8=A1=E6=9D=BF=E5=8D=B3=E5=8F=AF?= =?UTF-8?q?=E7=94=9F=E6=88=90(=E5=85=A5=E5=8F=A3=E5=BD=92=E4=B8=80?= =?UTF-8?q?=E7=BC=BA=E7=9C=81=20templateId=3Dgeneric)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:W-CLEAN 废玩法模板层后 template/list 返空,创作页「开始生成」因 canSubmit 依赖 「必须选中模板」而恒禁用;后端 @NotBlank/同源白名单/派发分支/落包一致性四道硬约束又要求 templateId 必填非空。核心思想=在入口把缺省 templateId 归一为 "generic"(恰好命中 SUPPORTED_TEMPLATE_IDS=[generic] 单一通用口径,品类区分留 prompt 层),四道约束以「恰好 generic」全部一致通过,DB 零改(template_id NOT NULL DEFAULT '' 归一后落 generic)、后端业务零改。 契约层(2):aigc.yaml AigcGenerateReqVO required 去 templateId、project.yaml ProjectCreateReqVO required 去 templateId,templateId 描述标注「可选,缺省 generic」。 后端层(归一 generic): - AigcGenerateReqVO/ProjectCreateReqVO/StudioDraftCreateReqVO 去 templateId @NotBlank + requiredMode REQUIRED→NOT_REQUIRED; - AigcTaskServiceImpl.submitGenerate 在 validateTemplateExists 前归一(validateTemplateExists 不改); - ProjectServiceImpl.createProject(BeanUtils.toBean 后)归一; - StudioServiceImpl.createDraft 原「空 templateId 抛 STUDIO_TEMPLATE_REQUIRED」改为归一 generic; - ProjectApi.createProject 的 templateId @Parameter required 放开(契约面一致避免 drift)。 前端 game-studio(去模板门):types.ts 两请求体 templateId 改可选;store/create.ts、api/aigc.ts generate 入参改可选;Create.vue canSubmit 去「必须选中模板」条件(prompt 非空即可提交)、 onSubmit 缺省传 'generic'(空态下也能提交)。 测试校正:StudioServiceImplTest「空模板抛错」→「空模板归一 generic 透传 project」(行为变更)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- contracts/api-schemas/aigc.yaml | 4 +-- contracts/api-schemas/project.yaml | 4 +-- .../app/task/vo/AigcGenerateReqVO.java | 8 ++++-- .../service/task/AigcTaskServiceImpl.java | 6 ++++ .../game/module/project/api/ProjectApi.java | 4 ++- .../app/project/vo/ProjectCreateReqVO.java | 7 +++-- .../service/project/ProjectServiceImpl.java | 5 ++++ .../app/studio/vo/StudioDraftCreateReqVO.java | 6 ++-- .../service/studio/StudioServiceImpl.java | 6 ++-- .../service/studio/StudioServiceImplTest.java | 28 ++++++++++++++----- game-studio/src/api/aigc.ts | 4 +-- game-studio/src/api/types.ts | 8 +++--- game-studio/src/store/create.ts | 4 +-- game-studio/src/views/create/Create.vue | 22 ++++++++++----- 14 files changed, 80 insertions(+), 36 deletions(-) diff --git a/contracts/api-schemas/aigc.yaml b/contracts/api-schemas/aigc.yaml index 6b6ac75b..883dea3e 100644 --- a/contracts/api-schemas/aigc.yaml +++ b/contracts/api-schemas/aigc.yaml @@ -197,10 +197,10 @@ components: # ---- 请求 VO ---- AigcGenerateReqVO: type: object - required: [prompt, templateId] + required: [prompt] properties: prompt: { type: string, minLength: 1, maxLength: 1000, description: 创作者一句话自然语言 Prompt(→ 契约#6 input.prompt) } - templateId: { type: string, description: '玩法模板 ID(→ 契约#6 input.templateId;可由前端从模板列表选,空则由 Dify 意图解析+模板匹配决定)' } + templateId: { type: string, description: '玩法模板 ID(→ 契约#6 input.templateId;可选,缺省=generic:W-CLEAN 废玩法模板层后,一句话创作无需选模板,入口将缺省 templateId 归一为 generic 单一通用口径,品类区分留 prompt 层;前端亦可显式从模板列表选)' } gameId: { type: integer, format: int64, description: '关联游戏 ID(project.game_project.id)。产物 GamePackage 经此落 game_version;studio 草稿场景传入' } versionId: { type: integer, format: int64, description: '关联版本 ID(project.game_version.id)。指定则产物写入该版本;为空时由生成流程新建版本' } attachments: diff --git a/contracts/api-schemas/project.yaml b/contracts/api-schemas/project.yaml index befaa9ca..69cef0b1 100644 --- a/contracts/api-schemas/project.yaml +++ b/contracts/api-schemas/project.yaml @@ -168,10 +168,10 @@ components: # ---- 请求/响应 VO ---- ProjectCreateReqVO: type: object - required: [title, templateId] + required: [title] properties: title: { type: string, maxLength: 60, description: 标题 } - templateId: { type: string, description: 玩法模板 ID } + templateId: { type: string, description: '玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后建项目无需选模板,入口将空 templateId 归一为 generic 单一通用口径)' } ProjectDraftReqVO: type: object properties: diff --git a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/controller/app/task/vo/AigcGenerateReqVO.java b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/controller/app/task/vo/AigcGenerateReqVO.java index bcc71c16..747c6ef6 100644 --- a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/controller/app/task/vo/AigcGenerateReqVO.java +++ b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/controller/app/task/vo/AigcGenerateReqVO.java @@ -22,9 +22,11 @@ public class AigcGenerateReqVO { @Size(max = 1000, message = "Prompt 不能超过 1000 个字符") private String prompt; - @Schema(description = "玩法模板 ID(→ 契约#6 input.templateId;空则由 Dify 意图解析匹配)", - requiredMode = Schema.RequiredMode.REQUIRED, example = "dodge") - @NotBlank(message = "玩法模板不能为空") + // generic 桥接(W-CLEAN 废玩法模板层后,一句话创作无需选模板):templateId 改可选; + // 缺省值由服务层入口 AigcTaskServiceImpl.submitGenerate 归一为 "generic"(恰好命中白名单), + // 故此处去掉 @NotBlank、requiredMode 由 REQUIRED 改 NOT_REQUIRED(契约 aigc.yaml AigcGenerateReqVO required 同步去掉 templateId)。 + @Schema(description = "玩法模板 ID(→ 契约#6 input.templateId;可选,缺省=generic,由服务层归一)", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "generic") private String templateId; @Schema(description = "关联游戏 ID(project.game_project.id);studio 草稿场景传入", example = "1024") diff --git a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/service/task/AigcTaskServiceImpl.java b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/service/task/AigcTaskServiceImpl.java index 9a22b1b6..493c6ef9 100644 --- a/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/service/task/AigcTaskServiceImpl.java +++ b/game-cloud/game-module-aigc/game-module-aigc-server/src/main/java/com/wanxiang/huijing/game/module/aigc/service/task/AigcTaskServiceImpl.java @@ -104,6 +104,12 @@ public class AigcTaskServiceImpl implements AigcTaskService { if (!StringUtils.hasText(reqVO.getPrompt())) { throw exception(AIGC_PROMPT_REQUIRED); } + // generic 桥接(W-CLEAN 废玩法模板层后,一句话创作无需选模板):缺省 templateId 归一为 "generic" + // (单一通用口径,品类区分留 prompt 层)。归一后恰好命中下方 validateTemplateExists 的同源白名单 + // SUPPORTED_TEMPLATE_IDS=[generic],@NotBlank 已下放、四道硬约束以「恰好 generic」一致通过;validateTemplateExists 本身不改。 + if (!StringUtils.hasText(reqVO.getTemplateId())) { + reqVO.setTemplateId("generic"); + } // 模板存在性校验 validateTemplateExists(reqVO.getTemplateId()); diff --git a/game-cloud/game-module-project/game-module-project-api/src/main/java/com/wanxiang/huijing/game/module/project/api/ProjectApi.java b/game-cloud/game-module-project/game-module-project-api/src/main/java/com/wanxiang/huijing/game/module/project/api/ProjectApi.java index 158e5473..de758f9f 100644 --- a/game-cloud/game-module-project/game-module-project-api/src/main/java/com/wanxiang/huijing/game/module/project/api/ProjectApi.java +++ b/game-cloud/game-module-project/game-module-project-api/src/main/java/com/wanxiang/huijing/game/module/project/api/ProjectApi.java @@ -96,7 +96,9 @@ public interface ProjectApi { @Operation(summary = "创建项目(studio 写类 RPC,owner=显式 creatorUserId)") @Parameter(name = "creatorUserId", description = "创作者用户 ID(项目 owner)", required = true, example = "99") @Parameter(name = "title", description = "标题", required = true, example = "我的放置小镇") - @Parameter(name = "templateId", description = "玩法模板 ID", required = true, example = "idle") + // generic 桥接(契约面一致避免 drift):templateId 改可选——空值由 project 侧 createProject 归一 generic; + // @RequestParam 不加 required=false 保持现状(调用方 studio 已归一后再传,恒非空),仅文档 @Parameter required 放开。 + @Parameter(name = "templateId", description = "玩法模板 ID(可选,缺省 generic)", required = false, example = "generic") CommonResult createProject(@RequestParam("creatorUserId") Long creatorUserId, @RequestParam("title") String title, @RequestParam("templateId") String templateId); diff --git a/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/controller/app/project/vo/ProjectCreateReqVO.java b/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/controller/app/project/vo/ProjectCreateReqVO.java index f2b2ccf2..1134b7f3 100644 --- a/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/controller/app/project/vo/ProjectCreateReqVO.java +++ b/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/controller/app/project/vo/ProjectCreateReqVO.java @@ -19,8 +19,11 @@ public class ProjectCreateReqVO { @Size(max = 60, message = "标题不能超过 60 个字符") private String title; - @Schema(description = "玩法模板 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "idle") - @NotBlank(message = "玩法模板不能为空") + // generic 桥接(W-CLEAN 废玩法模板层后建项目无需选模板):templateId 改可选; + // 缺省值由服务层入口 ProjectServiceImpl.createProject 在 BeanUtils.toBean 后归一为 "generic" + // (DB template_id 是 NOT NULL DEFAULT '',归一后落 generic;契约 project.yaml ProjectCreateReqVO required 同步去掉 templateId)。 + @Schema(description = "玩法模板 ID(可选,缺省 generic,由服务层归一)", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "generic") private String templateId; } diff --git a/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/service/project/ProjectServiceImpl.java b/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/service/project/ProjectServiceImpl.java index 69f26143..ece1419f 100644 --- a/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/service/project/ProjectServiceImpl.java +++ b/game-cloud/game-module-project/game-module-project-server/src/main/java/com/wanxiang/huijing/game/module/project/service/project/ProjectServiceImpl.java @@ -103,6 +103,11 @@ public class ProjectServiceImpl implements ProjectService { playerApi.validateCreator(userId).getCheckedData(); // 拷贝 title/templateId,其余服务端字段显式置默认值 ProjectDO project = BeanUtils.toBean(reqVO, ProjectDO.class); + // generic 桥接(W-CLEAN 废玩法模板层后建项目无需选模板):缺省 templateId 归一为 "generic" + // (单一通用口径)。DB template_id 是 NOT NULL DEFAULT '',归一后落 generic;@NotBlank 已下放,本归一兜服务端。 + if (!StringUtils.hasText(project.getTemplateId())) { + project.setTemplateId("generic"); + } project.setCreatorUserId(userId); project.setStatus(ProjectStatusEnum.DRAFT.getStatus()); // 初始草稿 project.setAgeRating("all"); // 默认全年龄,发布前可改 diff --git a/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/controller/app/studio/vo/StudioDraftCreateReqVO.java b/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/controller/app/studio/vo/StudioDraftCreateReqVO.java index 54234a1d..dd74d0f0 100644 --- a/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/controller/app/studio/vo/StudioDraftCreateReqVO.java +++ b/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/controller/app/studio/vo/StudioDraftCreateReqVO.java @@ -22,8 +22,10 @@ public class StudioDraftCreateReqVO { @Size(max = 128, message = "标题不能超过 128 个字符") private String title; - @Schema(description = "玩法模板 ID(透传 project/aigc)", requiredMode = Schema.RequiredMode.REQUIRED, example = "idle") - @NotBlank(message = "玩法模板不能为空") + // generic 桥接(W-CLEAN 废玩法模板层后建草稿无需选模板,契约面一致避免 drift):templateId 改可选; + // 缺省值由服务层入口 StudioServiceImpl.createDraft 归一为 "generic" 后透传 project/aigc(恰好命中白名单)。 + @Schema(description = "玩法模板 ID(透传 project/aigc;可选,缺省=generic,由服务层归一)", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "generic") private String templateId; @Schema(description = "一句话自然语言 Prompt(建草稿时可空,generate 时必填)", example = "做一个躲避陨石的小游戏") diff --git a/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImpl.java b/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImpl.java index 88b1b19c..e64b2ec4 100644 --- a/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImpl.java +++ b/game-cloud/game-module-studio/game-module-studio-server/src/main/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImpl.java @@ -83,9 +83,11 @@ public class StudioServiceImpl implements StudioService { // 创作者白名单可信边界校验(2026-06-10 鉴权件 §6.3,拍板4):非 A2 白名单创作者直接拒(抛 1-002-090-003), // 置于建草稿最前,避免非白名单用户触达后续 project 建游戏副作用。校验失败由 .getCheckedData() 触发异常向上传播。 playerApi.validateCreator(userId).getCheckedData(); - // 模板必填兜底校验(VO 已 @NotBlank,此处服务端兜底) + // generic 桥接(W-CLEAN 废玩法模板层后建草稿无需选模板):缺省 templateId 归一为 "generic" + // (单一通用口径,品类区分留 prompt 层),再透传 project/aigc(恰好命中 aigc 同源白名单 SUPPORTED_TEMPLATE_IDS=[generic])。 + // 注:原「空 templateId 抛 STUDIO_TEMPLATE_REQUIRED」行为已改为归一 generic(不再拒空)。 if (!StringUtils.hasText(reqVO.getTemplateId())) { - throw exception(STUDIO_TEMPLATE_REQUIRED); + reqVO.setTemplateId("generic"); } // R2 硬前置:调 project createProject 建游戏拿 gameId(不接受 game_id 恒空兜底);显式传 creatorUserId 作 owner Long gameId; diff --git a/game-cloud/game-module-studio/game-module-studio-server/src/test/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImplTest.java b/game-cloud/game-module-studio/game-module-studio-server/src/test/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImplTest.java index 0d7a5f9b..05c3886b 100644 --- a/game-cloud/game-module-studio/game-module-studio-server/src/test/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImplTest.java +++ b/game-cloud/game-module-studio/game-module-studio-server/src/test/java/com/wanxiang/huijing/game/module/studio/service/studio/StudioServiceImplTest.java @@ -108,13 +108,27 @@ class StudioServiceImplTest extends BaseMockitoUnitTest { } @Test - void testCreateDraft_templateRequired_throws() { - stubCreatorPass(); // 白名单先过(校验在模板兜底之前),再触发模板为空兜底 - // 模板为空 → 服务端兜底校验抛错,不调 project - ServiceException ex = assertThrows(ServiceException.class, - () -> studioService.createDraft(draftReq("t", ""), USER_ID)); - assertEquals(STUDIO_TEMPLATE_REQUIRED.getCode(), ex.getCode()); - verify(projectApi, never()).createProject(any(), any(), any()); + void testCreateDraft_emptyTemplate_normalizedToGeneric() { + // generic 桥接(行为变更):原「空 templateId 抛 STUDIO_TEMPLATE_REQUIRED」已改为归一 "generic" 后透传 project。 + // 本用例校正为新不变量:空 templateId → 归一 generic,以 generic 调 project createProject 并落会话。 + stubCreatorPass(); // 白名单放行(§6.3) + when(projectApi.createProject(eq(USER_ID), eq("t"), eq("generic"))) + .thenReturn(CommonResult.success(2048L)); + doAnswer(inv -> { + StudioSessionDO d = inv.getArgument(0); + d.setId(512L); + return 1; + }).when(studioSessionMapper).insert(any(StudioSessionDO.class)); + + StudioSessionDO session = studioService.createDraft(draftReq("t", ""), USER_ID); + + // 断言:空模板被归一 generic 透传 project,且会话 templateId 落 generic + assertEquals(2048L, session.getGameId()); + assertEquals("generic", session.getTemplateId()); + verify(projectApi).createProject(USER_ID, "t", "generic"); + ArgumentCaptor captor = ArgumentCaptor.forClass(StudioSessionDO.class); + verify(studioSessionMapper).insert(captor.capture()); + assertEquals("generic", captor.getValue().getTemplateId()); } // ============================== 创作者白名单校验(2026-06-10 鉴权件 §6.3,拍板4)============================== diff --git a/game-studio/src/api/aigc.ts b/game-studio/src/api/aigc.ts index 51a9efdf..ab9d7882 100644 --- a/game-studio/src/api/aigc.ts +++ b/game-studio/src/api/aigc.ts @@ -20,12 +20,12 @@ export function getTemplates(): Promise { * 提交生成任务(异步入队,立即返回 taskId)。 * POST /app-api/aigc/generate * @param prompt 一句话自然语言 Prompt - * @param templateId 玩法模板 ID(可空) + * @param templateId 玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后由后端归一) * @param extra 关联草稿信息(gameId/versionId/attachments,可选) */ export function generate( prompt: string, - templateId: string, + templateId?: string, extra?: Pick, ): Promise { const body: AigcGenerateReq = { prompt, templateId, ...extra } diff --git a/game-studio/src/api/types.ts b/game-studio/src/api/types.ts index 474fead1..bb43eb7b 100644 --- a/game-studio/src/api/types.ts +++ b/game-studio/src/api/types.ts @@ -141,8 +141,8 @@ export type AigcFailureReason = export interface AigcGenerateReq { /** 一句话自然语言 Prompt(1-1000 字) */ prompt: string - /** 玩法模板 ID(可空,空则由 Dify 意图解析决定) */ - templateId: string + /** 玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后由后端归一,前端可不传或传 'generic') */ + templateId?: string /** 关联游戏 ID(studio 草稿场景传入) */ gameId?: number /** 关联版本 ID(指定则写入该版本,空则新建版本) */ @@ -294,8 +294,8 @@ export interface Project { export interface ProjectCreateReq { /** 标题(≤60) */ title: string - /** 玩法模板 ID */ - templateId: string + /** 玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后由后端归一,前端可不传或传 'generic') */ + templateId?: string } /** 保存草稿请求体(project.yaml ProjectDraftReqVO) */ diff --git a/game-studio/src/store/create.ts b/game-studio/src/store/create.ts index 7539ef3c..48eb97be 100644 --- a/game-studio/src/store/create.ts +++ b/game-studio/src/store/create.ts @@ -75,12 +75,12 @@ export const useCreateStore = defineStore('create', () => { /** * 提交生成任务并自动开始轮询,返回 taskId(供路由跳转 /create/task/:taskId)。 * @param prompt 一句话 Prompt - * @param templateId 玩法模板 ID(可空) + * @param templateId 玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后由后端归一) * @param extra 关联草稿信息(gameId/versionId) */ async function generate( prompt: string, - templateId: string, + templateId?: string, extra?: { gameId?: number; versionId?: number }, ): Promise { const resp = await aigcApi.generate(prompt, templateId, extra) diff --git a/game-studio/src/views/create/Create.vue b/game-studio/src/views/create/Create.vue index cbe448af..9955d3e7 100644 --- a/game-studio/src/views/create/Create.vue +++ b/game-studio/src/views/create/Create.vue @@ -49,9 +49,13 @@ const selectedTemplate = computed(() => createStore.templates.find((t) => t.templateId === selectedTemplateId.value), ) -/** 是否可提交:选了模板 + Prompt 非空 + 不在提交中 */ +/** + * 是否可提交:Prompt 非空 + 不在提交中。 + * generic 桥接(W-CLEAN 废玩法模板层后,一句话创作无需选模板):去掉「必须选中模板」条件—— + * 后端缺省 templateId 归一 generic,故空模板(template/list 返空走「升级中」空态)下也能提交。 + */ const canSubmit = computed( - () => !!selectedTemplateId.value && prompt.value.trim().length > 0 && !submitting.value, + () => prompt.value.trim().length > 0 && !submitting.value, ) /** 加载模板列表(进入页面 / 失败重试时调用) */ @@ -101,17 +105,21 @@ async function onSubmit() { if (!canSubmit.value) return submitting.value = true try { - // 1) 建草稿项目(title 取 Prompt 前 30 字兜底,模板必填) - // 契约 ProjectCreateReqVO required:[title, templateId] + // generic 桥接(W-CLEAN 废玩法模板层后无需选模板):缺省 templateId 传 'generic'(后端亦会归一), + // 兼容用户未选模板(template/list 返空、空态)与显式选中两种情况。 + const templateId = selectedTemplateId.value || 'generic' + + // 1) 建草稿项目(title 取 Prompt 前 30 字兜底) + // 契约 ProjectCreateReqVO required:[title](templateId 可选,缺省 generic) const title = prompt.value.trim().slice(0, 30) || selectedTemplate.value?.name || '未命名作品' const projectId = await projectStore.create({ title, - templateId: selectedTemplateId.value, + templateId, }) // 2) 投递生成任务,关联草稿(gameId=projectId);store 内部已自动开始轮询。 - // 契约 AigcGenerateReqVO:prompt + templateId(+ gameId 关联草稿)。 - const taskId = await createStore.generate(prompt.value.trim(), selectedTemplateId.value, { + // 契约 AigcGenerateReqVO:prompt(+ gameId 关联草稿;templateId 可选,缺省 generic)。 + const taskId = await createStore.generate(prompt.value.trim(), templateId, { gameId: projectId, })