diff --git a/.memory/worklog.json b/.memory/worklog.json index c033a10..47c3459 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1755,6 +1755,13 @@ "type": "session-heartbeat", "message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 15:05 (~2)", "files_changed": 2 + }, + { + "ts": "2026-05-13T15:11:45+08:00", + "type": "commit", + "message": "auto-save 2026-05-13 15:11 (~3)", + "hash": "02df0c5", + "files_changed": 3 } ] } diff --git a/api/main.py b/api/main.py index d3a1c4b..ed6170b 100644 --- a/api/main.py +++ b/api/main.py @@ -73,6 +73,17 @@ class StoryboardScene(BaseModel): reference_ids: list[str] = [] # 参考图:选用该分镜里已提取的 element ids 作 reference +class StoryboardImage(BaseModel): + """用户从各处"上推"到分镜头编排区的图片""" + ref_id: str # uuid hex 8 + kind: Literal["keyframe", "cutout"] # keyframe = 关键帧本身 / cutout = 元素提取图 + frame_idx: int + element_id: str | None = None # cutout 时 + cutout_id: str | None = None # cutout 时(versioned id;老数据可能 == element_id) + label: str = "" # 显示用名字 + created_at: float = 0.0 + + class KeyElement(BaseModel): """关键帧里识别 / 用户提取的元素 · 多次提取累积多张图,让用户挑选满意的""" id: str # uuid hex 8 @@ -121,6 +132,7 @@ class Job(BaseModel): height: int = 0 frames: list[KeyFrame] = Field(default_factory=list) transcript: list[TranscriptSegment] = Field(default_factory=list) + storyboard_images: list[StoryboardImage] = Field(default_factory=list) error: str = "" @@ -1434,6 +1446,55 @@ def update_storyboard(job_id: str, idx: int, req: UpdateStoryboardReq) -> Job: return job +class PushStoryboardImageReq(BaseModel): + kind: Literal["keyframe", "cutout"] + frame_idx: int + element_id: str | None = None + cutout_id: str | None = None + label: str = "" + + +@app.post("/jobs/{job_id}/storyboard-images", response_model=Job) +def push_storyboard_image(job_id: str, req: PushStoryboardImageReq) -> Job: + """把一张图(关键帧本身或元素提取图)推送到分镜头编排区""" + import time as _time + job = JOBS.get(job_id) + if not job: + raise HTTPException(404, "job not found") + # 防重复推送:相同 frame_idx + element_id + cutout_id 已存在就跳过 + for existing in job.storyboard_images: + if (existing.kind == req.kind + and existing.frame_idx == req.frame_idx + and existing.element_id == req.element_id + and existing.cutout_id == req.cutout_id): + return job + img = StoryboardImage( + ref_id=uuid.uuid4().hex[:8], + kind=req.kind, + frame_idx=req.frame_idx, + element_id=req.element_id, + cutout_id=req.cutout_id, + label=req.label.strip(), + created_at=_time.time(), + ) + update(job, storyboard_images=job.storyboard_images + [img], message=f"上推到分镜头编排 · {req.label or req.kind}") + return job + + +@app.delete("/jobs/{job_id}/storyboard-images/{ref_id}", response_model=Job) +def remove_storyboard_image(job_id: str, ref_id: str) -> Job: + """从分镜头编排区移除一张图""" + job = JOBS.get(job_id) + if not job: + raise HTTPException(404, "job not found") + before = len(job.storyboard_images) + new_list = [x for x in job.storyboard_images if x.ref_id != ref_id] + if len(new_list) == before: + raise HTTPException(404, "storyboard image not found") + update(job, storyboard_images=new_list, message="从分镜头编排移除一张图") + return job + + @app.get("/jobs/{job_id}/frames/{idx}/elements/{element_id}/cutouts/{cutout_id}.jpg") def get_cutout_versioned(job_id: str, idx: int, element_id: str, cutout_id: str): p = job_dir(job_id) / "elements" / f"{idx:03d}_{element_id}_{cutout_id}.jpg" diff --git a/web/app/page.tsx b/web/app/page.tsx index c6b4d02..78852d0 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -304,11 +304,8 @@ export default function Home() { - {/* 左侧:竖向 tile 看板(极窄) */} -