polishing

This commit is contained in:
2026-05-06 10:53:27 -06:00
parent 09ebcbc9ec
commit fd6697b48e
18 changed files with 889 additions and 145 deletions

View File

@ -617,105 +617,114 @@ export default function App() {
<>
<div className="fixed inset-0 z-40" onClick={() => setShowFileMenu(false)} />
<div className="absolute left-0 top-full mt-1 z-50 w-44 rounded-lg border border-editor-border bg-editor-surface shadow-xl py-1">
<DropdownItem icon={<FilePlus2 className="w-4 h-4" />} label="New Project" onClick={() => { setShowFileMenu(false); handleNewProject(); }} />
<DropdownItem icon={<FolderOpen className="w-4 h-4" />} label="Open File" onClick={() => { setShowFileMenu(false); handleOpenFile(); }} />
<DropdownItem icon={<FileInput className="w-4 h-4" />} label="Load Project" onClick={() => { setShowFileMenu(false); handleLoadProject(); }} />
<DropdownItem icon={<FilePlus2 className="w-4 h-4" />} label="New Project" title="Start a new empty project" onClick={() => { setShowFileMenu(false); handleNewProject(); }} />
<DropdownItem icon={<FolderOpen className="w-4 h-4" />} label="Open File" title="Open a video or audio file for transcription" onClick={() => { setShowFileMenu(false); handleOpenFile(); }} />
<DropdownItem icon={<FileInput className="w-4 h-4" />} label="Load Project" title="Open a saved .aive project file" onClick={() => { setShowFileMenu(false); handleLoadProject(); }} />
<div className="border-t border-editor-border my-1" />
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save" onClick={() => { setShowFileMenu(false); handleSaveProject(); }} disabled={words.length === 0} />
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save As" onClick={() => { setShowFileMenu(false); handleSaveProjectAs(); }} disabled={words.length === 0} />
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save" title="Save current project" onClick={() => { setShowFileMenu(false); handleSaveProject(); }} disabled={words.length === 0} />
<DropdownItem icon={<Save className="w-4 h-4" />} label="Save As" title="Save a copy of the current project" onClick={() => { setShowFileMenu(false); handleSaveProjectAs(); }} disabled={words.length === 0} />
</div>
</>
)}
</div>
<ToolbarButton
icon={<Scissors className="w-4 h-4" />}
label="Cut"
onClick={handleCut}
active={cutMode}
disabled={!canEdit}
/>
<ToolbarButton
icon={<VolumeX className="w-4 h-4" />}
label="Mute"
onClick={handleMute}
active={muteMode}
disabled={!canEdit}
/>
<div className="flex items-center gap-1">
<ToolbarButton
icon={<SlidersHorizontal className="w-4 h-4" />}
label="Gain Zone"
onClick={handleGain}
active={gainMode}
icon={<Scissors className="w-4 h-4" />}
label="Cut"
onClick={handleCut}
active={cutMode}
disabled={!canEdit}
title="Cut selected word range or mark in/out area — removes the segment from output"
/>
<input
type="number"
min={-24}
max={24}
step={0.5}
value={gainModeDb}
onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
title="Gain dB for new gain zones"
disabled={!canEdit}
/>
</div>
<div className="flex items-center gap-1">
<ToolbarButton
icon={<Gauge className="w-4 h-4" />}
label="Speed Zone"
onClick={handleSpeed}
active={speedMode}
icon={<VolumeX className="w-4 h-4" />}
label="Mute"
onClick={handleMute}
active={muteMode}
disabled={!canEdit}
title="Mute selected word range or mark in/out area — silences audio, keeps video"
/>
<input
type="number"
min={0.25}
max={4}
step={0.05}
value={speedModeValue}
onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
title="Playback rate for new speed zones"
disabled={!canEdit}
<div className="flex items-center gap-1">
<ToolbarButton
icon={<SlidersHorizontal className="w-4 h-4" />}
label="Gain Zone"
onClick={handleGain}
active={gainMode}
disabled={!canEdit}
title="Add gain zone from selection or mark in/out — adjust volume up or down"
/>
<input
type="number"
min={-24}
max={24}
step={0.5}
value={gainModeDb}
onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Volume adjustment in decibels for new gain zones — positive boosts, negative reduces"
disabled={!canEdit}
/>
</div>
<div className="flex items-center gap-1">
<ToolbarButton
icon={<Gauge className="w-4 h-4" />}
label="Speed Zone"
onClick={handleSpeed}
active={speedMode}
disabled={!canEdit}
title="Add speed zone from selection or mark in/out — change playback speed"
/>
<input
type="number"
min={0.25}
max={4}
step={0.05}
value={speedModeValue}
onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Playback speed multiplier for new speed zones — 1x is normal, 2x is double speed"
disabled={!canEdit}
/>
</div>
<ToolbarButton
icon={<Grid3x3 className="w-4 h-4" />}
label="Zones"
active={activePanel === 'zones'}
onClick={() => togglePanel('zones')}
disabled={!videoPath || !canEdit}
title="Open zone editor panel — view and manage all cut, mute, gain, and speed zones"
/>
<ToolbarButton
icon={<span className="text-[10px] font-semibold">PA</span>}
label="Pause Trim"
active={activePanel === 'silence'}
onClick={() => togglePanel('silence')}
disabled={!videoPath || !canEdit}
title="Detect and remove silent pauses — batch-removes silence above a configurable threshold"
/>
<ToolbarButton
icon={<MapPin className="w-4 h-4" />}
label="Markers"
active={activePanel === 'markers'}
onClick={() => togglePanel('markers')}
disabled={!videoPath || !canEdit}
title="Add and manage timeline markers — chapter points, key moments, YouTube timestamps"
/>
<ToolbarButton
icon={<Music className="w-4 h-4" />}
label="Music"
active={activePanel === 'music'}
onClick={() => togglePanel('music')}
disabled={!videoPath || !canEdit}
title="Add background music track with auto-ducking — music lowers when someone speaks"
/>
<ToolbarButton
icon={<ListVideo className="w-4 h-4" />}
label="Append"
active={activePanel === 'append'}
onClick={() => togglePanel('append')}
disabled={!videoPath || !canEdit}
title="Append additional video clips — concatenate multiple files during export"
/>
</div>
<ToolbarButton
icon={<Grid3x3 className="w-4 h-4" />}
label="Zones"
active={activePanel === 'zones'}
onClick={() => togglePanel('zones')}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<span className="text-[10px] font-semibold">PA</span>}
label="Pause Trim"
active={activePanel === 'silence'}
onClick={() => togglePanel('silence')}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<MapPin className="w-4 h-4" />}
label="Markers"
active={activePanel === 'markers'}
onClick={() => togglePanel('markers')}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<Music className="w-4 h-4" />}
label="Music"
active={activePanel === 'music'}
onClick={() => togglePanel('music')}
disabled={!videoPath || !canEdit}
/>
<ToolbarButton
icon={<ListVideo className="w-4 h-4" />}
label="Append"
active={activePanel === 'append'}
onClick={() => togglePanel('append')}
disabled={!videoPath || !canEdit}
/>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-editor-surface border border-editor-border">
<select
value={whisperModel}
@ -745,7 +754,7 @@ export default function App() {
<button
onClick={handleReprocessProject}
disabled={isTranscribing || !videoPath || !canEdit}
title="Reprocess transcript with selected model"
data-tooltip="Re-run transcription with the selected Whisper model — replaces current transcript"
className="flex items-center gap-1 px-2 py-1 rounded text-xs text-editor-text hover:bg-editor-bg disabled:opacity-40 disabled:cursor-not-allowed"
>
<RefreshCw className={`w-3 h-3 ${isTranscribing ? 'animate-spin' : ''}`} />
@ -758,6 +767,7 @@ export default function App() {
active={activePanel === 'ai'}
onClick={() => togglePanel('ai')}
disabled={words.length === 0 || !canEdit}
title="AI filler detection, clip suggestions, and transcript analysis"
/>
<ToolbarButton
icon={<Download className="w-4 h-4" />}
@ -964,27 +974,30 @@ function ToolbarButton({
active,
onClick,
disabled,
title,
}: {
icon: React.ReactNode;
label: string;
active?: boolean;
onClick: () => void;
disabled?: boolean;
title?: string;
}) {
return (
<button
onClick={onClick}
disabled={disabled}
title={label}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors ${
active
? 'bg-editor-accent text-white'
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-surface'
} ${disabled ? 'opacity-40 cursor-not-allowed' : ''}`}
>
{icon}
{label}
</button>
<span data-tooltip={title || label}>
<button
onClick={onClick}
disabled={disabled}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors ${
active
? 'bg-editor-accent text-white'
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-surface'
} ${disabled ? 'opacity-40 cursor-not-allowed' : ''}`}
>
{icon}
{label}
</button>
</span>
);
}
@ -993,24 +1006,28 @@ function DropdownItem({
label,
onClick,
disabled,
title,
}: {
icon: React.ReactNode;
label: string;
onClick: () => void;
disabled?: boolean;
title?: string;
}) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors ${
disabled
? 'opacity-40 cursor-not-allowed'
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-bg'
}`}
>
{icon}
{label}
</button>
<span data-tooltip={title || label}>
<button
onClick={onClick}
disabled={disabled}
className={`w-full flex items-center gap-2 px-3 py-1.5 text-xs text-left transition-colors ${
disabled
? 'opacity-40 cursor-not-allowed'
: 'text-editor-text-muted hover:text-editor-text hover:bg-editor-bg'
}`}
>
{icon}
{label}
</button>
</span>
);
}