import sys def patch_file(): with open('frontend/src/App.tsx', 'r', encoding='utf-8') as f: content = f.read() # 1. Add state variables old1 = ''' const [job, setJob] = useState(null); const [outputs, setOutputs] = useState([]); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const videoRef = useRef(null);''' new1 = ''' const [job, setJob] = useState(null); const [outputs, setOutputs] = useState([]); const [isUploading, setIsUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [error, setError] = useState(null); type SegmentRow = { kind: "intro" | "core" | "outro"; label: string; start: number; end: number; startEditable: boolean; endEditable: boolean; coreIndex?: number; }; const [isLinked, setIsLinked] = useState(true); const [customSegmentRows, setCustomSegmentRows] = useState([]); const videoRef = useRef(null);''' if old1 not in content: print('Failed old1') content = content.replace(old1, new1) # 2. Remove duplicate SegmentRow type declaration old2 = ''' type SegmentRow = { kind: "intro" | "core" | "outro"; label: string; start: number; end: number; startEditable: boolean; endEditable: boolean; coreIndex?: number; }; const introBoundary = useMemo(() => {''' new2 = ''' const introBoundary = useMemo(() => {''' if old2 not in content: print('Failed old2') content = content.replace(old2, new2) # 3. Rename segmentRows to derivedSegmentRows old3 = ''' const segmentRows = useMemo(() => { if (!duration || !project || coreBoundaries.length < 2) return [] as SegmentRow[];''' new3 = ''' const derivedSegmentRows = useMemo(() => { if (!duration || !project || coreBoundaries.length < 2) return [] as SegmentRow[];''' if old3 not in content: print('Failed old3') content = content.replace(old3, new3) # 4. Add segmentRows constant old4 = ''' }); return rows; }, [duration, project, coreBoundaries, introBoundary, alignedOutroBoundary, lastFrameTime, frameStep, segments]); useEffect(() => { if (!segmentRows.length) {''' new4 = ''' }); return rows; }, [duration, project, coreBoundaries, introBoundary, alignedOutroBoundary, lastFrameTime, frameStep, segments]); const segmentRows = isLinked ? derivedSegmentRows : customSegmentRows; useEffect(() => { if (!segmentRows.length) {''' if old4 not in content: print('Failed old4') content = content.replace(old4, new4) # 5. handleUpload changes old5 = ''' const handleUpload = async () => { if (!project) { setError("Configure project settings before uploading."); setProjectDrawerOpen(true); return; } if (videoFiles.length === 0) return; setError(null); setIsUploading(true); try { const uploadedItems = await uploadVideos(videoFiles);''' new5 = ''' const handleUpload = async () => { if (!project) { setError("Configure project settings before uploading."); setProjectDrawerOpen(true); return; } if (videoFiles.length === 0) return; setError(null); setIsUploading(true); setUploadProgress(0); try { const uploadedItems = await uploadVideos(videoFiles, (index, total, progress) => { setUploadProgress((index + progress) / total); });''' if old5 not in content: print('Failed old5') content = content.replace(old5, new5) # 6. handleUpload linked reset old6 = ''' setJob(null); setOutputPrefix(uploaded.filename.replace(/\.[^.]+$/, "")); } setVideoFiles([]);''' new6 = ''' setJob(null); setOutputPrefix(uploaded.filename.replace(/\.[^.]+$/, "")); setIsLinked(true); } setVideoFiles([]);''' if old6 not in content: print('Failed old6') content = content.replace(old6, new6) # 7. handleSelectVideo reset old7 = ''' setOutputPrefix(item.filename.replace(/\.[^.]+$/, "")); setVideoFiles([]); setPreviewUrl(""); try {''' new7 = ''' setOutputPrefix(item.filename.replace(/\.[^.]+$/, "")); setVideoFiles([]); setPreviewUrl(""); setIsLinked(true); try {''' if old7 not in content: print('Failed old7') content = content.replace(old7, new7) # 8. commitSegmentDraft bypass old8 = ''' const snapped = snapToFrame(parsed); let boundaryIndex = -1; let boundaryValue = snapped;''' new8 = ''' const snapped = snapToFrame(parsed); if (!isLinked) { setCustomSegmentRows((prev) => { const next = [...prev]; next[rowIndex] = { ...next[rowIndex], [field]: snapped }; return next; }); setPendingSegmentEditPersist(true); return; } let boundaryIndex = -1; let boundaryValue = snapped;''' if old8 not in content: print('Failed old8') content = content.replace(old8, new8) # 9. handleDeleteSegment bypass old9 = ''' const handleDeleteSegment = async (row: SegmentRow) => { setError(null); if (row.kind === "intro") {''' new9 = ''' const handleDeleteSegment = async (row: SegmentRow) => { setError(null); if (!isLinked) { setCustomSegmentRows((prev) => prev.filter((r) => r !== row)); setPendingSegmentEditPersist(true); return; } if (row.kind === "intro") {''' if old9 not in content: print('Failed old9') content = content.replace(old9, new9) # 10. upload progress UI old10 = ''' ''' new10 = ''' {isUploading && ( Uploading... {Math.round(uploadProgress * 100)}% )} ''' if old10 not in content: print('Failed old10') content = content.replace(old10, new10) # 11. segments table unlink button old11 = ''' ''' new11 = ''' ''' if old11 not in content: print('Failed old11') content = content.replace(old11, new11) with open('frontend/src/App.tsx', 'w', encoding='utf-8') as f: f.write(content) patch_file()