feat: Settings modal with danger zone admin operations

Backend:
- POST /api/admin/clear-waveforms — deletes all cached waveform SVGs
- POST /api/admin/delete-all-clips — nuclear option: deletes all clips,
  MP3s, artwork, and waveforms. Returns deleted count.
- database.py: delete_all_clips() for bulk DB wipe
- Clip deletion cascade-removes waveform SVGs

Frontend:
- SettingsModal.tsx: center-stage modal with backdrop blur (bg-black/60
  backdrop-blur-sm). Danger zone with red-styled border and header.
- Two-step confirmation for Delete All (click once to arm, click again
  to confirm). Shows clip count dynamically.
- Gear icon trigger in header bar
- App.tsx: handleClearWaveforms + handleDeleteAll handlers

Docs:
- Architecture KB updated with album art, waveform, settings modal sections
- Tasks KB updated with T-021 (waveform) and TASK-001 (album art) marked done
- T-026 (settings modal) added to backlog
This commit is contained in:
rin.kagamine committed 2026-05-29 20:21:05 -04:00
1 parent 744f0ae6e5
commit e74c89e1b0
5 files changed
+219 -209

No files matched your search

+5
View File
@@ -151,4 +151,9 @@ async def remove_artwork(clip_id: str):
async def delete_clip(clip_id: str):
async with aiosqlite.connect(DB_PATH) as db:
await db.execute("DELETE FROM clips WHERE id = ?", (clip_id,))
await db.commit()
async def delete_all_clips():
async with aiosqlite.connect(DB_PATH) as db:
await db.execute("DELETE FROM clips")
await db.commit()
+37 -1
View File
@@ -10,7 +10,7 @@ from dotenv import load_dotenv
load_dotenv(Path(__file__).parent.parent / ".env")
from models import GenerateRequest, GenerateResponse, ClipList, RenameRequest, LYRIA_MODELS, DEFAULT_MODEL
from database import init_db, create_clip, get_clips, get_clip, delete_clip, update_clip_status, rename_clip, set_artwork, remove_artwork
from database import init_db, create_clip, get_clips, get_clip, delete_clip, update_clip_status, rename_clip, set_artwork, remove_artwork, delete_all_clips
from lyria_client import generate_clip, save_clip_file
from waveform import generate_waveform_svg, get_waveform_path
@@ -226,6 +226,42 @@ async def rename_clip_endpoint(clip_id: str, request: RenameRequest):
updated = await get_clip(clip_id)
return updated
# ─── Admin Endpoints ──────────────────────────────────────────────
@app.post("/api/admin/clear-waveforms")
async def clear_waveforms():
"""Delete all cached waveform SVGs. They regenerate on next view."""
from waveform import WAVEFORM_DIR
count = 0
if WAVEFORM_DIR.exists():
for f in WAVEFORM_DIR.glob("*.svg"):
f.unlink()
count += 1
return {"cleared": count}
@app.post("/api/admin/delete-all-clips")
async def delete_all_clips_endpoint():
"""Delete all clips, MP3s, artwork, and waveforms. Nuclear option."""
# Get all clips to clean up files
all_clips = await get_clips()
for clip in all_clips:
# Delete MP3
mp3_path = CLIPS_DIR / clip["filename"]
if mp3_path.exists():
mp3_path.unlink()
# Delete artwork
if clip.get("artwork_filename"):
art_path = ARTWORK_DIR / clip["artwork_filename"]
if art_path.exists():
art_path.unlink()
# Delete waveform
wf_path = get_waveform_path(clip["id"])
if wf_path and wf_path.exists():
wf_path.unlink()
# Clear DB
await delete_all_clips()
return {"deleted": len(all_clips)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)