import { useState, useEffect } from 'react' interface GenConfig { model: string has_api_key: boolean safety_harassment: string safety_hate_speech: string safety_sexually_explicit: string safety_dangerous: string } interface GenModel { id: string name: string description: string status: string } interface Props { isOpen: boolean onClose: () => void onClearWaveforms: () => Promise onDeleteAll: () => Promise clipCount: number } const SAFETY_OPTIONS = [ { value: 'OFF', label: 'Off' }, { value: 'BLOCK_NONE', label: 'Block None' }, { value: 'BLOCK_ONLY_HIGH', label: 'Block Few (High only)' }, { value: 'BLOCK_MEDIUM_AND_ABOVE', label: 'Block Some (Medium+)' }, { value: 'BLOCK_LOW_AND_ABOVE', label: 'Block Most (Low+)' }, ] function useGenSettings(apiPrefix: string) { const [models, setModels] = useState([]) const [config, setConfig] = useState(null) const [edited, setEdited] = useState>({}) const [expanded, setExpanded] = useState(false) const [saving, setSaving] = useState(false) const [saveMessage, setSaveMessage] = useState('') const fetchModels = async () => { try { const res = await fetch(`/api/${apiPrefix}/models`) if (res.ok) { const data = await res.json() setModels(data.models) } } catch (e) { console.error(`Failed to fetch ${apiPrefix} models:`, e) } } const fetchConfig = async () => { try { const res = await fetch(`/api/${apiPrefix}/config`) if (res.ok) { const data = await res.json() setConfig(data) } } catch (e) { console.error(`Failed to fetch ${apiPrefix} config:`, e) } } const save = async () => { if (!edited || Object.keys(edited).length === 0) return setSaving(true) try { const res = await fetch(`/api/${apiPrefix}/config`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(edited), }) if (res.ok) { setSaveMessage('Settings saved!') setConfig(prev => prev ? { ...prev, ...edited } : prev) setEdited({}) setTimeout(() => setSaveMessage(''), 2000) } else { setSaveMessage('Failed to save') } } catch (e) { setSaveMessage('Failed to save') } finally { setSaving(false) } } const update = (key: string, value: string) => { setEdited(prev => ({ ...prev, [key]: value })) setSaveMessage('') } const getValue = (key: keyof GenConfig): string => { const val = edited[key] ?? config?.[key] return typeof val === 'string' ? val : 'BLOCK_ONLY_HIGH' } const hasChanges = Object.keys(edited).length > 0 return { models, config, edited, expanded, setExpanded, saving, saveMessage, fetchModels, fetchConfig, save, update, getValue, hasChanges, } } export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDeleteAll, clipCount }: Props) { const [confirmDelete, setConfirmDelete] = useState(false) const [loading, setLoading] = useState(false) const [dangerExpanded, setDangerExpanded] = useState(false) const imageGen = useGenSettings('image-gen') const textGen = useGenSettings('text-gen') useEffect(() => { if (isOpen) { imageGen.fetchModels() imageGen.fetchConfig() textGen.fetchModels() textGen.fetchConfig() } }, [isOpen]) if (!isOpen) return null const handleClearWaveforms = async () => { setLoading(true) try { await onClearWaveforms() } finally { setLoading(false) } } const handleDeleteAll = async () => { if (!confirmDelete) { setConfirmDelete(true) return } setLoading(true) try { await onDeleteAll() setConfirmDelete(false) } finally { setLoading(false) } } const renderGenSection = ( title: string, icon: React.ReactNode, gen: ReturnType ) => (
{gen.expanded && (
{/* Model Selection */}
{/* API Key Status */}
{gen.config?.has_api_key ? 'API key configured' : 'No API key — set in .env'}
{/* Safety Settings */}

Safety Filters

{[ { key: 'safety_harassment', label: 'Harassment' }, { key: 'safety_hate_speech', label: 'Hate Speech' }, { key: 'safety_sexually_explicit', label: 'Sexually Explicit' }, { key: 'safety_dangerous', label: 'Dangerous Content' }, ].map(({ key, label }) => (
))}
{/* Save Button */}
{gen.saveMessage && ( {gen.saveMessage} )}

Changes apply immediately but reset on server restart. Set .env for persistence.

)}
) return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

Settings

{/* Content */}
{/* Image Generation */} {renderGenSection('Image Generation', ( ), imageGen)} {/* Text Generation */} {renderGenSection('Text Generation', ( ), textGen)} {/* Danger Zone */}
{dangerExpanded && (
{/* Clear Waveforms */}

Clear Waveform Cache

Delete all cached SVGs. They regenerate on next view.


{/* Delete All */}

Delete All Tracks

Remove {clipCount} clip{clipCount !== 1 ? 's' : ''}. Cannot be undone.

{confirmDelete && (

Click again to confirm permanent deletion.

)}
)}
) }