From a79b2806dc8e4d2c2e291aebbbf35752d287ce5b Mon Sep 17 00:00:00 2001 From: Kagamine Rin Date: Fri, 29 May 2026 22:15:34 -0400 Subject: [PATCH] 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 --- backend/src/main.py | 17 +++++ frontend/src/components/SettingsModal.tsx | 78 +++++++++++++++++++---- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/backend/src/main.py b/backend/src/main.py index cef2e20..9fe2a94 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -298,6 +298,23 @@ async def get_image_gen_configuration(): "has_api_key": bool(config.get("api_key")), } +@app.post("/api/image-gen/config") +async def update_image_gen_configuration(request: dict): + """Update image generation configuration (in-memory only, not persisted to .env).""" + # Update environment variables in-memory for this process + if "model" in request: + os.environ["IMAGE_GEN_MODEL"] = request["model"] + if "safety_harassment" in request: + os.environ["IMAGE_GEN_SAFETY_HARASSMENT"] = request["safety_harassment"] + if "safety_hate_speech" in request: + os.environ["IMAGE_GEN_SAFETY_HATE_SPEECH"] = request["safety_hate_speech"] + if "safety_sexually_explicit" in request: + os.environ["IMAGE_GEN_SAFETY_SEXUALLY_EXPLICIT"] = request["safety_sexually_explicit"] + if "safety_dangerous" in request: + os.environ["IMAGE_GEN_SAFETY_DANGEROUS"] = request["safety_dangerous"] + + return {"status": "updated"} + @app.patch("/api/clips/{clip_id}") async def rename_clip_endpoint(clip_id: str, request: RenameRequest): clip = await get_clip(clip_id) diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 8366443..2b16ecc 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -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([]) const [imageGenConfig, setImageGenConfig] = useState(null) + const [editedConfig, setEditedConfig] = useState>({}) 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 (
{/* Backdrop */} @@ -150,12 +191,8 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel -

- Configure via IMAGE_GEN_MODEL in .env -

{/* API Key Status */} @@ -189,8 +223,8 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel ))} -

- Configure via IMAGE_GEN_SAFETY_* in .env -

+ + {/* Save Button */} +
+ + {saveMessage && ( + + {saveMessage} + + )} +
+

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

)}