From 1f6f8c21fde7ff2cb4586d19d570b7079c711d40 Mon Sep 17 00:00:00 2001 From: nuclearspike Date: Sun, 26 Jul 2026 02:25:57 -0600 Subject: [PATCH] Fix timeline gap frame conditioning --- frontend/views/editor/GapGenerationModal.tsx | 62 ++++++++++++++++--- .../VideoEditorTimelineEditingPanel.tsx | 39 +++++++----- frontend/views/editor/editor-actions.ts | 5 +- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/frontend/views/editor/GapGenerationModal.tsx b/frontend/views/editor/GapGenerationModal.tsx index b63c7c7b5..52fd6d4a5 100644 --- a/frontend/views/editor/GapGenerationModal.tsx +++ b/frontend/views/editor/GapGenerationModal.tsx @@ -17,6 +17,16 @@ interface TimelineGap { type GapGenerateMode = 'text-to-video' | 'image-to-video' | 'text-to-image' +export interface GapFrameConditioning { + imagePath: string | null + imageFile: File | null + reverseResult: boolean +} + +interface FrameOverride { + file: File + previewUrl: string +} interface GapGenerationModalProps { selectedGap: TimelineGap | null @@ -41,7 +51,7 @@ interface GapGenerationModalProps { regenStatusMessage: string regenProgress: number regenReset: () => void - handleGapGenerate: () => void + handleGapGenerate: (conditioning: GapFrameConditioning) => void handleCloseGap: () => void setSelectedGap: (gap: TimelineGap | null) => void gapApplyAudioToTrack: boolean @@ -119,8 +129,8 @@ export function GapGenerationModal({ const [startFrameEnabled, setStartFrameEnabled] = useState(true) const [endFrameEnabled, setEndFrameEnabled] = useState(false) - const [startFrameOverride, setStartFrameOverride] = useState(null) - const [endFrameOverride, setEndFrameOverride] = useState(null) + const [startFrameOverride, setStartFrameOverride] = useState(null) + const [endFrameOverride, setEndFrameOverride] = useState(null) const startFrameInputRef = useRef(null) const endFrameInputRef = useRef(null) @@ -131,22 +141,54 @@ export function GapGenerationModal({ setEndFrameOverride(null) }, [gapGenerateMode]) - const displayedBeforeFrame = startFrameOverride ?? gapBeforeFrame - const displayedAfterFrame = endFrameOverride ?? gapAfterFrame + const displayedBeforeFrame = startFrameOverride?.previewUrl + ?? (gapBeforeFrame ? pathToFileUrl(gapBeforeFrame) : null) + const displayedAfterFrame = endFrameOverride?.previewUrl + ?? (gapAfterFrame ? pathToFileUrl(gapAfterFrame) : null) const handleFrameFileChange = ( e: React.ChangeEvent, - setter: (v: string | null) => void, + setter: (v: FrameOverride | null) => void, onSelect: () => void ) => { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() - reader.onload = (ev) => { setter(ev.target?.result as string); onSelect() } + reader.onload = (ev) => { + setter({ file, previewUrl: ev.target?.result as string }) + onSelect() + } reader.readAsDataURL(file) e.target.value = '' } + const handleGenerate = () => { + if (!isVideoMode || gapImageFile) { + handleGapGenerate({ imagePath: null, imageFile: null, reverseResult: false }) + return + } + + if (startFrameEnabled && (startFrameOverride || gapBeforeFrame)) { + handleGapGenerate({ + imagePath: startFrameOverride ? null : gapBeforeFrame, + imageFile: startFrameOverride?.file ?? null, + reverseResult: false, + }) + return + } + + if (endFrameEnabled && (endFrameOverride || gapAfterFrame)) { + handleGapGenerate({ + imagePath: endFrameOverride ? null : gapAfterFrame, + imageFile: endFrameOverride?.file ?? null, + reverseResult: true, + }) + return + } + + handleGapGenerate({ imagePath: null, imageFile: null, reverseResult: false }) + } + return ( <> {gapGenerateMode && ( @@ -229,7 +271,7 @@ export function GapGenerationModal({ onClick={() => { if (startFrameEnabled) { setStartFrameEnabled(false) } else { setStartFrameEnabled(true); setEndFrameEnabled(false) } }} > { if (endFrameEnabled) { setEndFrameEnabled(false) } else { setEndFrameEnabled(true); setStartFrameEnabled(false) } }} >