auto-save 2026-05-13 11:23 (~4)

This commit is contained in:
2026-05-13 11:23:24 +08:00
parent f4ce533b8d
commit 647b05a5bb
4 changed files with 35 additions and 12 deletions

View File

@@ -1323,6 +1323,13 @@
"type": "session-heartbeat", "type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 2 项未提交变更 · 最近提交auto-save 2026-05-13 11:12 (~1)", "message": "Claude 会话活跃 · 最近命令claude · 2 项未提交变更 · 最近提交auto-save 2026-05-13 11:12 (~1)",
"files_changed": 2 "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
} }
] ]
} }

View File

@@ -1037,10 +1037,22 @@ def cleanup_frame(job_id: str, idx: int, req: CleanupReq | None = None) -> Job:
if not frame_path.exists(): if not frame_path.exists():
raise HTTPException(404, "frame file missing") raise HTTPException(404, "frame file missing")
region_phrase = _region_to_phrase(req.region) if (req and req.region) else "" region_phrases: list[str] = []
if region_phrase: 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 = ( 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." "Keep all other parts unchanged."
) )
else: else:

View File

@@ -30,9 +30,11 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
const [addingZh, setAddingZh] = useState(false) const [addingZh, setAddingZh] = useState(false)
const [addInput, setAddInput] = useState("") const [addInput, setAddInput] = useState("")
const [mounted, setMounted] = useState(false) 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 [cropMode, setCropMode] = useState(false)
const [region, setRegion] = useState<{ x: number; y: number; w: number; h: number } | null>(null) const [regions, setRegions] = useState<Region[]>([])
const [draftRegion, setDraftRegion] = useState<Region | null>(null) // 当前正在拖的
const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null) const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null)
const [extractNamePrompt, setExtractNamePrompt] = useState(false) // 提取模式:要用户填名字 const [extractNamePrompt, setExtractNamePrompt] = useState(false) // 提取模式:要用户填名字
const [extractName, setExtractName] = useState("") const [extractName, setExtractName] = useState("")
@@ -43,7 +45,8 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
// 切换分镜时清空选区 // 切换分镜时清空选区
useEffect(() => { useEffect(() => {
setCropMode(false) setCropMode(false)
setRegion(null) setRegions([])
setDraftRegion(null)
setDragStart(null) setDragStart(null)
setExtractNamePrompt(false) setExtractNamePrompt(false)
setExtractName("") 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) setCleaning(true)
try { 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) onJobUpdate?.(updated)
toast.success(`分镜 ${f.index + 1} 清洗完成 · 下方查看${withRegion ? "(框内)" : ""}`) toast.success(`分镜 ${f.index + 1} 清洗完成${usable && usable.length > 0 ? `${usable.length} 个区域)` : ""}`)
if (withRegion) { setCropMode(false); setRegion(null) } if (useRegions) { setCropMode(false); setRegions([]); setDraftRegion(null) }
} catch (e) { } catch (e) {
toast.error("清洗失败:" + (e instanceof Error ? e.message : String(e))) toast.error("清洗失败:" + (e instanceof Error ? e.message : String(e)))
} finally { } finally {

View File

@@ -212,12 +212,12 @@ export function cutoutUrl(jobId: string, frameIndex: number, elementId: string):
export async function cleanupFrame( export async function cleanupFrame(
jobId: string, jobId: string,
frameIdx: number, frameIdx: number,
region?: { x: number; y: number; w: number; h: number } | null, regions?: Array<{ x: number; y: number; w: number; h: number }> | null,
): Promise<Job> { ): Promise<Job> {
const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/cleanup`, { const res = await fetch(`${API_BASE}/jobs/${jobId}/frames/${frameIdx}/cleanup`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ region: region ?? null }), body: JSON.stringify({ regions: regions ?? null }),
}) })
if (!res.ok) { if (!res.ok) {
const txt = await res.text().catch(() => "") const txt = await res.text().catch(() => "")