auto-save 2026-05-14 05:49 (~2)

This commit is contained in:
2026-05-14 05:49:26 +08:00
parent 1b3148d296
commit a98639a33e
2 changed files with 40 additions and 30 deletions

View File

@@ -1,19 +1,5 @@
{
"entries": [
{
"files_changed": 9,
"hash": "b0ffd03",
"message": "auto-save 2026-05-12 16:02 (+2, ~6)",
"ts": "2026-05-12T16:05:47+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "cc31bfe",
"message": "auto-save 2026-05-12 16:11 (~1)",
"ts": "2026-05-12T16:11:20+08:00",
"type": "commit"
},
{
"files_changed": 4,
"hash": "35b3278",
@@ -3358,6 +3344,19 @@
"type": "session-heartbeat",
"message": "Claude 会话活跃 · 最近命令claude · 3 项未提交变更 · 最近提交auto-save 2026-05-14 05:38 (~3)",
"files_changed": 3
},
{
"ts": "2026-05-14T05:43:54+08:00",
"type": "commit",
"message": "auto-save 2026-05-14 05:43 (~3)",
"hash": "1b3148d",
"files_changed": 3
},
{
"ts": "2026-05-13T21:48:51Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 2 项未提交变更 · 最近提交auto-save 2026-05-14 05:43 (~3)",
"files_changed": 2
}
]
}

View File

@@ -58,7 +58,7 @@ const LIGHTBOX_TABS: Array<{ key: LightboxTab; label: string }> = [
export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, onChange, onToggleSelect, onJobUpdate, onSwitchPanel, onCopyImage, embedded = false }: Props) {
const [describing, setDescribing] = useState(false)
const [cleaning, setCleaning] = useState(false)
const [cleaningFrameIds, setCleaningFrameIds] = useState<Set<number>>(new Set())
const [batchCleaning, setBatchCleaning] = useState(false)
const [batchCleanupProgress, setBatchCleanupProgress] = useState<{ done: number; total: number; failed: number } | null>(null)
const [applying, setApplying] = useState(false)
@@ -83,7 +83,9 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
const [draftRegion, setDraftRegion] = useState<Region | null>(null) // 当前正在拖的
const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null)
const imgWrapRef = useRef<HTMLDivElement>(null)
const activeIndexRef = useRef<number | null>(activeIndex)
useEffect(() => setMounted(true), [])
useEffect(() => { activeIndexRef.current = activeIndex }, [activeIndex])
// 切换分镜时清空选区
useEffect(() => {
@@ -116,6 +118,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
const elements = f.elements ?? []
const hasCleaned = !!f.cleaned_url
const latestSceneAsset = f.scene_assets?.[f.scene_assets.length - 1] ?? null
const isCleaningCurrentFrame = cleaningFrameIds.has(f.index)
const cleanedFrameCount = frames.filter((frame) => frame.cleaned_applied || frame.cleaned_url).length
const pendingCleanFrames = frames.filter((frame) => !frame.cleaned_applied && !frame.cleaned_url)
const selectedFrameIndices = Array.from(selected).sort((a, b) => a - b)
@@ -140,22 +143,30 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
}
const handleCleanup = async (useRegions = false) => {
setCleaning(true)
const frameIdx = f.index
const usable = useRegions ? regions.filter((r) => r.w >= 0.03 && r.h >= 0.03) : null
setCleaningFrameIds((prev) => new Set(prev).add(frameIdx))
try {
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)
const updated = await cleanupFrame(jobId, frameIdx, usable && usable.length > 0 ? usable : null)
onJobUpdate?.(updated)
toast.success(`分镜 ${f.index + 1} 清洗完成${usable && usable.length > 0 ? `${usable.length} 个区域)` : ""}`)
if (useRegions) { setCropMode(false); setRegions([]); setDraftRegion(null) }
toast.success(`分镜 ${frameIdx + 1} 清洗完成${usable && usable.length > 0 ? `${usable.length} 个区域)` : ""}`)
if (useRegions && activeIndexRef.current === frameIdx) {
setCropMode(false); setRegions([]); setDraftRegion(null)
}
} catch (e) {
toast.error("清洗失败:" + (e instanceof Error ? e.message : String(e)))
} finally {
setCleaning(false)
setCleaningFrameIds((prev) => {
const next = new Set(prev)
next.delete(frameIdx)
return next
})
}
}
const handleCleanupAllFrames = async () => {
const targets = pendingCleanFrames.length > 0 ? pendingCleanFrames : frames
const targets = (pendingCleanFrames.length > 0 ? pendingCleanFrames : frames)
.filter((frame) => !cleaningFrameIds.has(frame.index))
if (targets.length === 0) return
setBatchCleaning(true)
setBatchCleanupProgress({ done: 0, total: targets.length, failed: 0 })
@@ -502,7 +513,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
<button
type="button"
onClick={handleCleanupAllFrames}
disabled={batchCleaning || cleaning || cropMode || frames.length === 0}
disabled={batchCleaning || cropMode || frames.length === 0}
className="w-full rounded-md bg-cyan-500/75 px-2 py-1.5 text-[11px] font-medium text-white transition hover:bg-cyan-400 disabled:cursor-wait disabled:opacity-45 inline-flex items-center justify-center gap-1.5"
title="自动清洗所有未处理关键帧;不满意的帧再手工框选清洗"
>
@@ -526,12 +537,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
<div className="flex items-center gap-1">
<button
onClick={() => handleCleanup(true)}
disabled={cleaning || batchCleaning || regions.length === 0}
disabled={isCleaningCurrentFrame || batchCleaning || regions.length === 0}
className="flex-1 px-1.5 py-1.5 rounded-md text-[10.5px] font-medium inline-flex items-center justify-center gap-1 transition bg-cyan-500 hover:bg-cyan-400 text-white disabled:opacity-40 disabled:cursor-not-allowed"
title="批量清洗所有框内"
>
{cleaning ? <Loader2 className="h-3 w-3 animate-spin" /> : <Sparkle className="h-3 w-3" />}
{cleaning ? "去掉中" : `去掉${regions.length > 1 ? ` ${regions.length}` : ""}`}
{isCleaningCurrentFrame ? <Loader2 className="h-3 w-3 animate-spin" /> : <Sparkle className="h-3 w-3" />}
{isCleaningCurrentFrame ? "去掉中" : `去掉${regions.length > 1 ? ` ${regions.length}` : ""}`}
</button>
<button
onClick={() => setRegions((prev) => prev.slice(0, -1))}
@@ -599,12 +610,12 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
{/* 清洗按钮(全图) */}
<button
onClick={() => handleCleanup(false)}
disabled={cleaning || batchCleaning || cropMode}
disabled={isCleaningCurrentFrame || batchCleaning || cropMode}
className="w-full px-3 py-1.5 rounded-md text-[11.5px] font-medium inline-flex items-center justify-center gap-1.5 transition bg-gradient-to-r from-cyan-500/80 to-emerald-500/80 hover:from-cyan-500 hover:to-emerald-500 text-white disabled:opacity-40 disabled:cursor-not-allowed"
title="清掉水印 / @用户名 / 字幕 / 平台 logo"
>
{cleaning ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkle className="h-3.5 w-3.5" />}
{cleaning ? "清洗中…5-15 秒)" : hasCleaned ? "重新清洗" : f.cleaned_applied ? "再次清洗" : "清洗水印"}
{isCleaningCurrentFrame ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkle className="h-3.5 w-3.5" />}
{isCleaningCurrentFrame ? "清洗中…5-15 秒)" : hasCleaned ? "重新清洗" : f.cleaned_applied ? "再次清洗" : "清洗水印"}
</button>
</>
)}
@@ -651,7 +662,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
<button
type="button"
onClick={handleGenerateSceneAsset}
disabled={sceneGenerating || cleaning || batchCleaning}
disabled={sceneGenerating || isCleaningCurrentFrame || batchCleaning}
className="w-full rounded-md bg-emerald-500/65 px-2 py-1.5 text-[11px] font-medium text-white transition hover:bg-emerald-400 disabled:cursor-wait disabled:opacity-45 inline-flex items-center justify-center gap-1"
title="生成一张去水印、高清增强后的场景参考图"
>