feat: Editable safety settings in SettingsModal
Backend: - New POST /api/image-gen/config endpoint to update image generation config in-memory (applies immediately, resets on server restart) - Accepts: model, safety_harassment, safety_hate_speech, safety_sexually_explicit, safety_dangerous Frontend: - SettingsModal safety filters are now editable dropdowns (no longer disabled) - Model selection is also editable - Added 'Save Changes' button with visual feedback: * Green 'Settings saved!' on success * Red 'Failed to save' on error * Disabled when no changes made - Tracks edited state vs. saved state - Note: changes apply immediately but reset on server restart (set .env for persistence) Fixed TypeScript issues: - getConfigValue() properly typed to return string only - Proper union type for config keys excluding has_api_key
This commit is contained in:
1 parent
6410e16912
commit
a79b2806dc
2 files changed
+81
-14
No files matched your search
@@ -35,14 +35,19 @@ const SAFETY_OPTIONS = [
|
||||
export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDeleteAll, clipCount }: Props) {
|
||||
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [imageGenModels, setImageGenModels] = useState<ImageGenModel[]>([])
|
||||
const [imageGenConfig, setImageGenConfig] = useState<ImageGenConfig | null>(null)
|
||||
const [editedConfig, setEditedConfig] = useState<Partial<ImageGenConfig>>({})
|
||||
const [showImageGenSettings, setShowImageGenSettings] = useState(false)
|
||||
const [saveMessage, setSaveMessage] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchImageGenModels()
|
||||
fetchImageGenConfig()
|
||||
setEditedConfig({})
|
||||
setSaveMessage('')
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
@@ -95,6 +100,42 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveImageGenConfig = async () => {
|
||||
if (!editedConfig || Object.keys(editedConfig).length === 0) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const res = await fetch('/api/image-gen/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(editedConfig),
|
||||
})
|
||||
if (res.ok) {
|
||||
setSaveMessage('Settings saved!')
|
||||
setImageGenConfig(prev => prev ? { ...prev, ...editedConfig } : prev)
|
||||
setEditedConfig({})
|
||||
setTimeout(() => setSaveMessage(''), 2000)
|
||||
} else {
|
||||
setSaveMessage('Failed to save')
|
||||
}
|
||||
} catch (e) {
|
||||
setSaveMessage('Failed to save')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const updateEditedConfig = (key: string, value: string) => {
|
||||
setEditedConfig(prev => ({ ...prev, [key]: value }))
|
||||
setSaveMessage('')
|
||||
}
|
||||
|
||||
const getConfigValue = (key: 'model' | 'safety_harassment' | 'safety_hate_speech' | 'safety_sexually_explicit' | 'safety_dangerous'): string => {
|
||||
const val = editedConfig[key] ?? imageGenConfig?.[key]
|
||||
return typeof val === 'string' ? val : 'BLOCK_ONLY_HIGH'
|
||||
}
|
||||
|
||||
const hasChanges = Object.keys(editedConfig).length > 0
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
@@ -150,12 +191,8 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
|
||||
<label className="block text-xs text-lyria-muted mb-1.5">Model</label>
|
||||
<select
|
||||
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-2 text-sm text-white focus:outline-none focus:border-lyria-accent"
|
||||
value={imageGenConfig?.model || 'gemini-3.1-flash-image-preview'}
|
||||
onChange={(e) => {
|
||||
// This would need a backend endpoint to update config
|
||||
// For now, it's read-only from .env
|
||||
console.log('Model selection changed:', e.target.value)
|
||||
}}
|
||||
value={getConfigValue('model')}
|
||||
onChange={(e) => updateEditedConfig('model', e.target.value)}
|
||||
>
|
||||
{imageGenModels.map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
@@ -163,9 +200,6 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-lyria-muted/60 mt-1">
|
||||
Configure via IMAGE_GEN_MODEL in .env
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* API Key Status */}
|
||||
@@ -189,8 +223,8 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
|
||||
<label className="block text-xs text-lyria-muted mb-1">{label}</label>
|
||||
<select
|
||||
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-1.5 text-sm text-white focus:outline-none focus:border-lyria-accent"
|
||||
value={imageGenConfig?.[key as keyof ImageGenConfig] as string || 'BLOCK_ONLY_HIGH'}
|
||||
disabled
|
||||
value={getConfigValue(key as 'safety_harassment' | 'safety_hate_speech' | 'safety_sexually_explicit' | 'safety_dangerous')}
|
||||
onChange={(e) => updateEditedConfig(key, e.target.value)}
|
||||
>
|
||||
{SAFETY_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
@@ -198,10 +232,26 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
<p className="text-xs text-lyria-muted/60">
|
||||
Configure via IMAGE_GEN_SAFETY_* in .env
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex items-center gap-3 pt-2">
|
||||
<button
|
||||
onClick={handleSaveImageGenConfig}
|
||||
disabled={!hasChanges || saving}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md transition-colors disabled:opacity-50 bg-lyria-accent text-black hover:bg-lyria-accent/90"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
{saveMessage && (
|
||||
<span className={`text-xs ${saveMessage.includes('Failed') ? 'text-red-400' : 'text-green-400'}`}>
|
||||
{saveMessage}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-lyria-muted/60">
|
||||
Changes apply immediately but reset on server restart. Set .env for persistence.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user