From a02c5eb48cd567e8bafcf835bfcd45ac41dcd080 Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 25 May 2026 14:46:36 +0800 Subject: [PATCH] fix: tolerate blank creative job requests --- api/main.py | 16 +++++++++++++++- docs/source-analysis.html | 14 +++++++++++++- web/lib/api.ts | 15 ++++++++++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/api/main.py b/api/main.py index 673678e..b15dcfe 100644 --- a/api/main.py +++ b/api/main.py @@ -5486,11 +5486,25 @@ def _write_creative_reference_frame(job_id: str, file_bytes: bytes | None = None @app.post("/creative/jobs/image", response_model=Job) -async def create_creative_image_job(request: Request, file: UploadFile | None = File(default=None)) -> Job: +async def create_creative_image_job(request: Request) -> Job: user = data_user_from_request(request) job_id = uuid.uuid4().hex[:12] file_bytes: bytes | None = None source_label = "blank" + file: UploadFile | None = None + content_type = request.headers.get("content-type", "").lower() + if "multipart/form-data" in content_type: + content_length = request.headers.get("content-length", "0") + if "boundary=" not in content_type and content_length in {"", "0"}: + file = None + else: + try: + form = await request.form() + except Exception as e: + raise HTTPException(400, f"invalid multipart body: {e}") + maybe_file = form.get("file") + if getattr(maybe_file, "filename", "") and hasattr(maybe_file, "read"): + file = maybe_file if file and file.filename: ext = Path(file.filename).suffix.lower() if ext not in {".jpg", ".jpeg", ".png", ".webp"}: diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 80462fe..80a10d5 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -627,7 +627,7 @@

后端核心

- + @@ -1183,6 +1183,18 @@ ProductRefStateItem {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-25 · 修复空白创作任务请求体解析失败

+ API + UI +
+
+

问题:文生图或文生视频没有上传首帧时,前端仍用空 FormDatacreateCreativeImageJob;部分浏览器 / 代理链路会把它变成缺 boundary 的 multipart/form-data,FastAPI 在进入 /creative/jobs/image 业务函数前直接返回 400 There was an error parsing the body

+

改动:web/lib/api.ts 在无文件时改发 JSON 空对象;有文件时才发 multipart。api/main.py/creative/jobs/image 不再通过 File(...) 强制预解析请求体,改为按 Content-Type 手动兼容 JSON / 无 body / 正常 multipart,并对空 multipart 容错为创建空白底图任务。

+

影响:首页四模式里文生图、文生视频这类无首帧路径都能稳定先创建轻量 job;首帧 / 首尾帧模式仍正常上传图片,坏图片继续返回 400。

+
+

2026-05-25 · 首页按模型能力暴露尺寸和时长

diff --git a/web/lib/api.ts b/web/lib/api.ts index db1b2aa..aea4920 100644 --- a/web/lib/api.ts +++ b/web/lib/api.ts @@ -378,9 +378,18 @@ export async function generateCreativeCopy(body: { } export async function createCreativeImageJob(file?: File | null): Promise { - const fd = new FormData() - if (file) fd.append("file", file) - const res = await fetch(`${API_BASE}/creative/jobs/image`, { method: "POST", body: fd }) + const init: RequestInit = file + ? (() => { + const fd = new FormData() + fd.append("file", file) + return { method: "POST", body: fd } + })() + : { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: "{}", + } + const res = await fetch(`${API_BASE}/creative/jobs/image`, init) if (!res.ok) { const txt = await res.text().catch(() => "") throw apiError("createCreativeImageJob", res.status, txt)
api/main.pyFastAPI 单文件后端:登录会话、状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、原音频转写/翻译、声音与背景音分析、后续口播改写/TTS、文件返回;同时承载全局 prompt_libraryasset_library 的磁盘索引、CRUD、删除保护和复制到 job API。轻量创作入口 POST /creative/jobs/image 把上传图片或空白底图写成一个只有 0 号关键帧的 Job,让首页直接复用生图/生视频接口;/health 返回 image_optionsimage_size_optionsvideo_optionsvideo_size_optionsvideo_duration_optionsvideo_max_duration_seconds/frames/{idx}/generatemodel 字段用于图片模型偏好,size 字段用于图片输出尺寸;/storyboard/video 继续使用 model 字段选择视频别名,并先校验画幅与时长能力边界。旧 AgentRun 一键出片状态机、TK 复刻接口和 POST /creative/copy 继续保留。
api/main.pyFastAPI 单文件后端:登录会话、状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、原音频转写/翻译、声音与背景音分析、后续口播改写/TTS、文件返回;同时承载全局 prompt_libraryasset_library 的磁盘索引、CRUD、删除保护和复制到 job API。轻量创作入口 POST /creative/jobs/image 把上传图片或空白底图写成一个只有 0 号关键帧的 Job,让首页直接复用生图/生视频接口;该接口兼容无 body / JSON 空对象 / 正常 multipart 上传,避免无首帧文生图或文生视频时空 multipart 被 FastAPI 在业务前置解析阶段拒绝;/health 返回 image_optionsimage_size_optionsvideo_optionsvideo_size_optionsvideo_duration_optionsvideo_max_duration_seconds/frames/{idx}/generatemodel 字段用于图片模型偏好,size 字段用于图片输出尺寸;/storyboard/video 继续使用 model 字段选择视频别名,并先校验画幅与时长能力边界。旧 AgentRun 一键出片状态机、TK 复刻接口和 POST /creative/copy 继续保留。
api/product_library/skg-products内置 SKG 白底产品图库:manifest.json 记录从桌面产品图筛出的 gallery 白底图和桌面 4 张产品角度图,images/ 存 45 张参考图。
api/character_library/skg-characters内置相似主体形象库:从桌面 5 套策划形象导入,manifest.json 记录运动阳光男、都市型男、优雅白领女、运动辣妹、绅士大叔,每套含 7 张透明骨架参考图和一段 prompt_brief。相似主体生成时优先使用文字 brief 作为创意方向,避免把内置图作为强参考图复制。
asset_library/全局素材库目录,和 jobs/ 平级,不写入任何 job state。四类目录为 subjectsproductsscenesvideos;每个素材自带 manifest.json 和图片/视频文件,index.json 只是启动扫描重建出来的缓存。库素材选用到 job 时必须复制文件到 jobs/<jobId>/assetsstoryboard-videos,禁止直接保存 library 引用。