diff --git a/.memory/worklog.json b/.memory/worklog.json index 866f425..e9679f1 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1323,6 +1323,13 @@ "type": "session-heartbeat", "message": "Claude 会话活跃 · 最近命令:claude · 2 项未提交变更 · 最近提交:auto-save 2026-05-13 11:12 (~1)", "files_changed": 2 + }, + { + "ts": "2026-05-13T11:17:52+08:00", + "type": "commit", + "message": "auto-save 2026-05-13 11:17 (~2)", + "hash": "f4ce533", + "files_changed": 2 } ] } diff --git a/api/main.py b/api/main.py index 7495626..fae3ab2 100644 --- a/api/main.py +++ b/api/main.py @@ -1037,10 +1037,22 @@ def cleanup_frame(job_id: str, idx: int, req: CleanupReq | None = None) -> Job: if not frame_path.exists(): raise HTTPException(404, "frame file missing") - region_phrase = _region_to_phrase(req.region) if (req and req.region) else "" - if region_phrase: + region_phrases: list[str] = [] + if req and req.regions: + for r in req.regions: + p = _region_to_phrase(r) + if p: + region_phrases.append(p) + # 去重保序 + region_phrases = list(dict.fromkeys(region_phrases)) + + if region_phrases: + if len(region_phrases) == 1: + zones = f"the {region_phrases[0]} part" + else: + zones = "these parts: " + ", ".join(region_phrases) prompt = ( - f"Erase the text and graphics in the {region_phrase} part of the image. " + f"Erase the text and graphics in {zones} of the image. " "Keep all other parts unchanged." ) else: diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index 91d0b35..801ed32 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -30,9 +30,11 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o const [addingZh, setAddingZh] = useState(false) const [addInput, setAddInput] = useState("") const [mounted, setMounted] = useState(false) - // 画框模式 + 选区(相对坐标 0-1) + // 画框模式 + 多选区(相对坐标 0-1) + type Region = { x: number; y: number; w: number; h: number } const [cropMode, setCropMode] = useState(false) - const [region, setRegion] = useState<{ x: number; y: number; w: number; h: number } | null>(null) + const [regions, setRegions] = useState([]) + const [draftRegion, setDraftRegion] = useState(null) // 当前正在拖的 const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null) const [extractNamePrompt, setExtractNamePrompt] = useState(false) // 提取模式:要用户填名字 const [extractName, setExtractName] = useState("") @@ -43,7 +45,8 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o // 切换分镜时清空选区 useEffect(() => { setCropMode(false) - setRegion(null) + setRegions([]) + setDraftRegion(null) setDragStart(null) setExtractNamePrompt(false) setExtractName("") @@ -90,13 +93,14 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o } } - const handleCleanup = async (withRegion = false) => { + const handleCleanup = async (useRegions = false) => { setCleaning(true) try { - const updated = await cleanupFrame(jobId, f.index, withRegion ? region : null) + const usable = useRegions ? regions.filter((r) => r.w >= 0.03 && r.h >= 0.03) : null + const updated = await cleanupFrame(jobId, f.index, usable && usable.length > 0 ? usable : null) onJobUpdate?.(updated) - toast.success(`分镜 ${f.index + 1} 清洗完成 · 下方查看${withRegion ? "(框内)" : ""}`) - if (withRegion) { setCropMode(false); setRegion(null) } + toast.success(`分镜 ${f.index + 1} 清洗完成${usable && usable.length > 0 ? `(${usable.length} 个区域)` : ""}`) + if (useRegions) { setCropMode(false); setRegions([]); setDraftRegion(null) } } catch (e) { toast.error("清洗失败:" + (e instanceof Error ? e.message : String(e))) } finally { diff --git a/web/lib/api.ts b/web/lib/api.ts index 47dc0b5..130d018 100644 --- a/web/lib/api.ts +++ b/web/lib/api.ts @@ -212,12 +212,12 @@ export function cutoutUrl(jobId: string, frameIndex: number, elementId: string): export async function cleanupFrame( jobId: string, frameIdx: number, - region?: { x: number; y: number; w: number; h: number } | null, + regions?: Array<{ x: number; y: number; w: number; h: number }> | null, ): Promise { const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/cleanup`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ region: region ?? null }), + body: JSON.stringify({ regions: regions ?? null }), }) if (!res.ok) { const txt = await res.text().catch(() => "")