From 0df967507f15429e4b956714976be10ff4d9bb67 Mon Sep 17 00:00:00 2001 From: dillonj Date: Sat, 11 Apr 2026 19:42:30 -0600 Subject: [PATCH] able to process audio with different model; new project button --- frontend/src/App.tsx | 112 +++++++++++++++++++++++++++++- frontend/src/store/editorStore.ts | 9 ++- frontend/src/types/project.ts | 1 + 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3d1cfe3..26b59f5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,6 +20,8 @@ import { Save, Scissors, VolumeX, + FilePlus2, + RefreshCw, } from 'lucide-react'; const IS_ELECTRON = !!window.electronAPI; @@ -31,12 +33,14 @@ export default function App() { const { videoPath, words, + transcriptionModel, isTranscribing, transcriptionProgress, transcriptionStatus, loadVideo, setBackendUrl, setTranscription, + setTranscriptionModel, setTranscribing, backendUrl, selectedWordIndices, @@ -49,6 +53,7 @@ export default function App() { const [whisperModel, setWhisperModel] = useState('base'); const [cutMode, setCutMode] = useState(false); const [muteMode, setMuteMode] = useState(false); + const [showReprocessConfirm, setShowReprocessConfirm] = useState(false); const fileInputRef = useRef(null); useKeyboardShortcuts(); @@ -137,6 +142,17 @@ export default function App() { } }; + const handleNewProject = () => { + const shouldReset = window.confirm('Start a new project? Unsaved changes in the current session will be lost.'); + if (!shouldReset) return; + + useEditorStore.getState().reset(); + setActivePanel(null); + setManualPath(''); + setCutMode(false); + setMuteMode(false); + }; + const handleManualSubmit = async (e: React.FormEvent) => { e.preventDefault(); const path = manualPath.trim(); @@ -177,6 +193,7 @@ export default function App() { setTranscribing(true, 20, 'Transcribing audio...'); const data = await window.electronAPI.transcribe(path, whisperModel); setTranscription(data); + setTranscriptionModel(whisperModel); } catch (err) { console.error('Transcription error:', err); alert(`Transcription failed. Check the console for details.\n\n${err}`); @@ -185,6 +202,22 @@ export default function App() { } }; + const handleReprocessProject = async () => { + if (!videoPath) return; + if (!window.electronAPI?.transcribe) { + alert('Reprocessing is only available in desktop mode.'); + return; + } + + setShowReprocessConfirm(true); + }; + + const confirmReprocessProject = async () => { + if (!videoPath) return; + setShowReprocessConfirm(false); + await transcribeVideo(videoPath); + }; + const togglePanel = (panel: Panel) => { setActivePanel((prev) => (prev === panel ? null : panel)); }; @@ -326,8 +359,18 @@ export default function App() { {videoPath.split(/[\\/]/).pop()} + {transcriptionModel && ( + + Model: {transcriptionModel} + + )} -
+
+ } + label="New" + onClick={handleNewProject} + /> } label="Open" @@ -367,6 +410,42 @@ export default function App() { onClick={() => togglePanel('silence')} disabled={!videoPath} /> +
+ + +
} label="AI" @@ -449,6 +528,37 @@ export default function App() { )}
{import.meta.env.DEV && } + + {showReprocessConfirm && ( +
setShowReprocessConfirm(false)} + > +
e.stopPropagation()} + > +

Reprocess transcript?

+

+ This will reprocess the current file with {whisperModel} and replace the current transcript words and timings. +

+
+ + +
+
+
+ )}
); } diff --git a/frontend/src/store/editorStore.ts b/frontend/src/store/editorStore.ts index 12eb83c..bc788a7 100644 --- a/frontend/src/store/editorStore.ts +++ b/frontend/src/store/editorStore.ts @@ -23,6 +23,7 @@ interface EditorState { cutRanges: CutRange[]; muteRanges: MuteRange[]; silenceTrimGroups: SilenceTrimGroup[]; + transcriptionModel: string | null; language: string; currentTime: number; @@ -45,6 +46,7 @@ interface EditorActions { setBackendUrl: (url: string) => void; loadVideo: (path: string) => void; setExportedAudioPath: (path: string | null) => void; + setTranscriptionModel: (model: string | null) => void; saveProject: () => ProjectFile; setTranscription: (result: TranscriptionResult) => void; setCurrentTime: (time: number) => void; @@ -85,6 +87,7 @@ const initialState: EditorState = { cutRanges: [], muteRanges: [], silenceTrimGroups: [], + transcriptionModel: null, language: '', currentTime: 0, duration: 0, @@ -136,8 +139,10 @@ export const useEditorStore = create()( setExportedAudioPath: (path) => set({ exportedAudioPath: path }), + setTranscriptionModel: (model) => set({ transcriptionModel: model }), + saveProject: (): ProjectFile => { - const { videoPath, words, segments, deletedRanges, cutRanges, muteRanges, silenceTrimGroups, language, exportedAudioPath } = get(); + const { videoPath, words, segments, deletedRanges, cutRanges, muteRanges, silenceTrimGroups, transcriptionModel, language, exportedAudioPath } = get(); if (!videoPath) throw new Error('No video loaded'); const now = new Date().toISOString(); // Strip globalStartIndex (runtime-only field) before persisting @@ -146,6 +151,7 @@ export const useEditorStore = create()( version: 1, videoPath, exportedAudioPath: exportedAudioPath ?? undefined, + transcriptionModel: transcriptionModel ?? undefined, words, segments: persistSegments as unknown as Segment[], deletedRanges, @@ -410,6 +416,7 @@ export const useEditorStore = create()( cutRanges: data.cutRanges || [], muteRanges: data.muteRanges || [], silenceTrimGroups: data.silenceTrimGroups || [], + transcriptionModel: data.transcriptionModel ?? null, language: data.language || '', exportedAudioPath: data.exportedAudioPath ?? null, }); diff --git a/frontend/src/types/project.ts b/frontend/src/types/project.ts index 2ae8592..cd3ca99 100644 --- a/frontend/src/types/project.ts +++ b/frontend/src/types/project.ts @@ -58,6 +58,7 @@ export interface ProjectFile { version: 1; videoPath: string; exportedAudioPath?: string; // path to modified/processed audio if it exists + transcriptionModel?: string; words: Word[]; segments: Segment[]; deletedRanges: DeletedRange[];