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:
1 parent
744f0ae6e5
commit
e74c89e1b0
5 files changed
+219
-209
No files matched your search
@@ -152,3 +152,8 @@ 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
@@ -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)
|
||||
@@ -1,203 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="160" viewBox="0 0 800 160" preserveAspectRatio="none">
|
||||
<rect width="800" height="160" fill="transparent" />
|
||||
<rect x="0.00" y="56.83" width="3.00" height="46.33" fill="hsl(20.0, 75.0%, 40.0%)" rx="1.50" />
|
||||
<rect x="4.00" y="59.36" width="3.00" height="41.28" fill="hsl(20.1, 75.1%, 40.1%)" rx="1.50" />
|
||||
<rect x="8.01" y="62.18" width="3.00" height="35.63" fill="hsl(20.2, 75.2%, 40.2%)" rx="1.50" />
|
||||
<rect x="12.02" y="61.82" width="3.00" height="36.35" fill="hsl(20.3, 75.2%, 40.3%)" rx="1.50" />
|
||||
<rect x="16.02" y="59.89" width="3.00" height="40.22" fill="hsl(20.4, 75.3%, 40.4%)" rx="1.50" />
|
||||
<rect x="20.02" y="59.29" width="3.00" height="41.42" fill="hsl(20.5, 75.4%, 40.5%)" rx="1.50" />
|
||||
<rect x="24.03" y="59.66" width="3.00" height="40.68" fill="hsl(20.6, 75.5%, 40.6%)" rx="1.50" />
|
||||
<rect x="28.04" y="56.40" width="3.00" height="47.21" fill="hsl(20.7, 75.5%, 40.7%)" rx="1.50" />
|
||||
<rect x="32.04" y="51.05" width="3.00" height="57.90" fill="hsl(20.8, 75.6%, 40.8%)" rx="1.50" />
|
||||
<rect x="36.05" y="52.57" width="3.00" height="54.86" fill="hsl(20.9, 75.7%, 40.9%)" rx="1.50" />
|
||||
<rect x="40.05" y="58.60" width="3.00" height="42.80" fill="hsl(21.0, 75.8%, 41.0%)" rx="1.50" />
|
||||
<rect x="44.05" y="59.61" width="3.00" height="40.78" fill="hsl(21.1, 75.8%, 41.1%)" rx="1.50" />
|
||||
<rect x="48.06" y="55.00" width="3.00" height="50.01" fill="hsl(21.2, 75.9%, 41.2%)" rx="1.50" />
|
||||
<rect x="52.06" y="48.42" width="3.00" height="63.15" fill="hsl(21.3, 76.0%, 41.3%)" rx="1.50" />
|
||||
<rect x="56.07" y="49.02" width="3.00" height="61.95" fill="hsl(21.4, 76.1%, 41.4%)" rx="1.50" />
|
||||
<rect x="60.07" y="53.93" width="3.00" height="52.15" fill="hsl(21.5, 76.1%, 41.5%)" rx="1.50" />
|
||||
<rect x="64.08" y="55.92" width="3.00" height="48.16" fill="hsl(21.6, 76.2%, 41.6%)" rx="1.50" />
|
||||
<rect x="68.08" y="55.96" width="3.00" height="48.08" fill="hsl(21.7, 76.3%, 41.7%)" rx="1.50" />
|
||||
<rect x="72.09" y="54.26" width="3.00" height="51.48" fill="hsl(21.8, 76.4%, 41.8%)" rx="1.50" />
|
||||
<rect x="76.09" y="54.78" width="3.00" height="50.43" fill="hsl(21.9, 76.4%, 41.9%)" rx="1.50" />
|
||||
<rect x="80.10" y="55.36" width="3.00" height="49.27" fill="hsl(22.0, 76.5%, 42.0%)" rx="1.50" />
|
||||
<rect x="84.11" y="54.71" width="3.00" height="50.57" fill="hsl(22.1, 76.6%, 42.1%)" rx="1.50" />
|
||||
<rect x="88.11" y="54.67" width="3.00" height="50.66" fill="hsl(22.2, 76.7%, 42.2%)" rx="1.50" />
|
||||
<rect x="92.11" y="56.41" width="3.00" height="47.17" fill="hsl(22.3, 76.7%, 42.3%)" rx="1.50" />
|
||||
<rect x="96.12" y="62.29" width="3.00" height="35.42" fill="hsl(22.4, 76.8%, 42.4%)" rx="1.50" />
|
||||
<rect x="100.12" y="68.10" width="3.00" height="23.80" fill="hsl(22.5, 76.9%, 42.5%)" rx="1.50" />
|
||||
<rect x="104.13" y="66.79" width="3.00" height="26.41" fill="hsl(22.6, 77.0%, 42.6%)" rx="1.50" />
|
||||
<rect x="108.13" y="58.40" width="3.00" height="43.19" fill="hsl(22.7, 77.0%, 42.7%)" rx="1.50" />
|
||||
<rect x="112.14" y="52.25" width="3.00" height="55.50" fill="hsl(22.8, 77.1%, 42.8%)" rx="1.50" />
|
||||
<rect x="116.14" y="51.41" width="3.00" height="57.17" fill="hsl(22.9, 77.2%, 42.9%)" rx="1.50" />
|
||||
<rect x="120.15" y="50.12" width="3.00" height="59.76" fill="hsl(23.0, 77.3%, 43.0%)" rx="1.50" />
|
||||
<rect x="124.16" y="46.97" width="3.00" height="66.07" fill="hsl(23.1, 77.3%, 43.1%)" rx="1.50" />
|
||||
<rect x="128.16" y="45.50" width="3.00" height="69.01" fill="hsl(23.2, 77.4%, 43.2%)" rx="1.50" />
|
||||
<rect x="132.16" y="46.25" width="3.00" height="67.50" fill="hsl(23.3, 77.5%, 43.3%)" rx="1.50" />
|
||||
<rect x="136.17" y="47.45" width="3.00" height="65.09" fill="hsl(23.4, 77.6%, 43.4%)" rx="1.50" />
|
||||
<rect x="140.17" y="47.58" width="3.00" height="64.83" fill="hsl(23.5, 77.6%, 43.5%)" rx="1.50" />
|
||||
<rect x="144.18" y="47.01" width="3.00" height="65.98" fill="hsl(23.6, 77.7%, 43.6%)" rx="1.50" />
|
||||
<rect x="148.19" y="46.09" width="3.00" height="67.82" fill="hsl(23.7, 77.8%, 43.7%)" rx="1.50" />
|
||||
<rect x="152.19" y="44.66" width="3.00" height="70.67" fill="hsl(23.8, 77.9%, 43.8%)" rx="1.50" />
|
||||
<rect x="156.19" y="43.38" width="3.00" height="73.24" fill="hsl(23.9, 77.9%, 43.9%)" rx="1.50" />
|
||||
<rect x="160.20" y="42.69" width="3.00" height="74.62" fill="hsl(24.0, 78.0%, 44.0%)" rx="1.50" />
|
||||
<rect x="164.20" y="42.96" width="3.00" height="74.07" fill="hsl(24.1, 78.1%, 44.1%)" rx="1.50" />
|
||||
<rect x="168.21" y="43.23" width="3.00" height="73.54" fill="hsl(24.2, 78.2%, 44.2%)" rx="1.50" />
|
||||
<rect x="172.22" y="43.14" width="3.00" height="73.71" fill="hsl(24.3, 78.2%, 44.3%)" rx="1.50" />
|
||||
<rect x="176.22" y="42.50" width="3.00" height="75.00" fill="hsl(24.4, 78.3%, 44.4%)" rx="1.50" />
|
||||
<rect x="180.22" y="43.18" width="3.00" height="73.64" fill="hsl(24.5, 78.4%, 44.5%)" rx="1.50" />
|
||||
<rect x="184.23" y="46.08" width="3.00" height="67.85" fill="hsl(24.6, 78.5%, 44.6%)" rx="1.50" />
|
||||
<rect x="188.23" y="47.71" width="3.00" height="64.57" fill="hsl(24.7, 78.5%, 44.7%)" rx="1.50" />
|
||||
<rect x="192.24" y="46.58" width="3.00" height="66.85" fill="hsl(24.8, 78.6%, 44.8%)" rx="1.50" />
|
||||
<rect x="196.25" y="44.47" width="3.00" height="71.06" fill="hsl(24.9, 78.7%, 44.9%)" rx="1.50" />
|
||||
<rect x="200.25" y="42.30" width="3.00" height="75.40" fill="hsl(25.0, 78.8%, 45.0%)" rx="1.50" />
|
||||
<rect x="204.25" y="39.07" width="3.00" height="81.86" fill="hsl(25.1, 78.8%, 45.1%)" rx="1.50" />
|
||||
<rect x="208.26" y="34.34" width="3.00" height="91.32" fill="hsl(25.2, 78.9%, 45.2%)" rx="1.50" />
|
||||
<rect x="212.26" y="28.58" width="3.00" height="102.85" fill="hsl(25.3, 79.0%, 45.3%)" rx="1.50" />
|
||||
<rect x="216.27" y="28.39" width="3.00" height="103.22" fill="hsl(25.4, 79.1%, 45.4%)" rx="1.50" />
|
||||
<rect x="220.28" y="34.34" width="3.00" height="91.33" fill="hsl(25.5, 79.1%, 45.5%)" rx="1.50" />
|
||||
<rect x="224.28" y="36.00" width="3.00" height="88.00" fill="hsl(25.6, 79.2%, 45.6%)" rx="1.50" />
|
||||
<rect x="228.28" y="34.64" width="3.00" height="90.71" fill="hsl(25.7, 79.3%, 45.7%)" rx="1.50" />
|
||||
<rect x="232.29" y="35.28" width="3.00" height="89.45" fill="hsl(25.8, 79.4%, 45.8%)" rx="1.50" />
|
||||
<rect x="236.29" y="34.58" width="3.00" height="90.83" fill="hsl(25.9, 79.4%, 45.9%)" rx="1.50" />
|
||||
<rect x="240.30" y="31.30" width="3.00" height="97.41" fill="hsl(26.0, 79.5%, 46.0%)" rx="1.50" />
|
||||
<rect x="244.31" y="30.42" width="3.00" height="99.16" fill="hsl(26.1, 79.6%, 46.1%)" rx="1.50" />
|
||||
<rect x="248.31" y="32.29" width="3.00" height="95.42" fill="hsl(26.2, 79.7%, 46.2%)" rx="1.50" />
|
||||
<rect x="252.31" y="33.14" width="3.00" height="93.73" fill="hsl(26.3, 79.7%, 46.3%)" rx="1.50" />
|
||||
<rect x="256.32" y="34.43" width="3.00" height="91.15" fill="hsl(26.4, 79.8%, 46.4%)" rx="1.50" />
|
||||
<rect x="260.32" y="37.39" width="3.00" height="85.23" fill="hsl(26.5, 79.9%, 46.5%)" rx="1.50" />
|
||||
<rect x="264.33" y="39.79" width="3.00" height="80.42" fill="hsl(26.6, 80.0%, 46.6%)" rx="1.50" />
|
||||
<rect x="268.33" y="40.72" width="3.00" height="78.55" fill="hsl(26.7, 80.1%, 46.7%)" rx="1.50" />
|
||||
<rect x="272.34" y="42.19" width="3.00" height="75.62" fill="hsl(26.8, 80.1%, 46.8%)" rx="1.50" />
|
||||
<rect x="276.34" y="44.26" width="3.00" height="71.48" fill="hsl(26.9, 80.2%, 46.9%)" rx="1.50" />
|
||||
<rect x="280.35" y="49.08" width="3.00" height="61.84" fill="hsl(27.0, 80.3%, 47.0%)" rx="1.50" />
|
||||
<rect x="284.36" y="58.09" width="3.00" height="43.82" fill="hsl(27.1, 80.4%, 47.1%)" rx="1.50" />
|
||||
<rect x="288.36" y="59.32" width="3.00" height="41.36" fill="hsl(27.2, 80.4%, 47.2%)" rx="1.50" />
|
||||
<rect x="292.37" y="48.56" width="3.00" height="62.89" fill="hsl(27.3, 80.5%, 47.3%)" rx="1.50" />
|
||||
<rect x="296.37" y="40.08" width="3.00" height="79.83" fill="hsl(27.4, 80.6%, 47.4%)" rx="1.50" />
|
||||
<rect x="300.38" y="38.30" width="3.00" height="83.41" fill="hsl(27.5, 80.7%, 47.5%)" rx="1.50" />
|
||||
<rect x="304.38" y="38.19" width="3.00" height="83.62" fill="hsl(27.6, 80.7%, 47.6%)" rx="1.50" />
|
||||
<rect x="308.38" y="38.52" width="3.00" height="82.96" fill="hsl(27.7, 80.8%, 47.7%)" rx="1.50" />
|
||||
<rect x="312.39" y="38.85" width="3.00" height="82.29" fill="hsl(27.8, 80.9%, 47.8%)" rx="1.50" />
|
||||
<rect x="316.39" y="39.23" width="3.00" height="81.55" fill="hsl(27.9, 81.0%, 47.9%)" rx="1.50" />
|
||||
<rect x="320.40" y="40.29" width="3.00" height="79.42" fill="hsl(28.0, 81.0%, 48.0%)" rx="1.50" />
|
||||
<rect x="324.40" y="40.20" width="3.00" height="79.60" fill="hsl(28.1, 81.1%, 48.1%)" rx="1.50" />
|
||||
<rect x="328.41" y="38.50" width="3.00" height="83.00" fill="hsl(28.2, 81.2%, 48.2%)" rx="1.50" />
|
||||
<rect x="332.41" y="38.64" width="3.00" height="82.73" fill="hsl(28.3, 81.3%, 48.3%)" rx="1.50" />
|
||||
<rect x="336.42" y="39.40" width="3.00" height="81.20" fill="hsl(28.4, 81.3%, 48.4%)" rx="1.50" />
|
||||
<rect x="340.43" y="39.40" width="3.00" height="81.21" fill="hsl(28.5, 81.4%, 48.5%)" rx="1.50" />
|
||||
<rect x="344.43" y="38.13" width="3.00" height="83.75" fill="hsl(28.6, 81.5%, 48.6%)" rx="1.50" />
|
||||
<rect x="348.44" y="34.46" width="3.00" height="91.08" fill="hsl(28.7, 81.6%, 48.7%)" rx="1.50" />
|
||||
<rect x="352.44" y="33.77" width="3.00" height="92.46" fill="hsl(28.8, 81.6%, 48.8%)" rx="1.50" />
|
||||
<rect x="356.44" y="36.67" width="3.00" height="86.67" fill="hsl(28.9, 81.7%, 48.9%)" rx="1.50" />
|
||||
<rect x="360.45" y="37.03" width="3.00" height="85.94" fill="hsl(29.0, 81.8%, 49.0%)" rx="1.50" />
|
||||
<rect x="364.45" y="33.79" width="3.00" height="92.42" fill="hsl(29.1, 81.9%, 49.1%)" rx="1.50" />
|
||||
<rect x="368.46" y="30.75" width="3.00" height="98.51" fill="hsl(29.2, 81.9%, 49.2%)" rx="1.50" />
|
||||
<rect x="372.46" y="30.43" width="3.00" height="99.15" fill="hsl(29.3, 82.0%, 49.3%)" rx="1.50" />
|
||||
<rect x="376.47" y="30.85" width="3.00" height="98.30" fill="hsl(29.4, 82.1%, 49.4%)" rx="1.50" />
|
||||
<rect x="380.47" y="28.85" width="3.00" height="102.30" fill="hsl(29.5, 82.2%, 49.5%)" rx="1.50" />
|
||||
<rect x="384.48" y="26.32" width="3.00" height="107.36" fill="hsl(29.6, 82.2%, 49.6%)" rx="1.50" />
|
||||
<rect x="388.49" y="27.37" width="3.00" height="105.26" fill="hsl(29.7, 82.3%, 49.7%)" rx="1.50" />
|
||||
<rect x="392.49" y="29.78" width="3.00" height="100.44" fill="hsl(29.8, 82.4%, 49.8%)" rx="1.50" />
|
||||
<rect x="396.50" y="30.69" width="3.00" height="98.63" fill="hsl(29.9, 82.5%, 49.9%)" rx="1.50" />
|
||||
<rect x="400.50" y="30.26" width="3.00" height="99.47" fill="hsl(30.1, 82.5%, 50.1%)" rx="1.50" />
|
||||
<rect x="404.50" y="30.11" width="3.00" height="99.77" fill="hsl(30.2, 82.6%, 50.2%)" rx="1.50" />
|
||||
<rect x="408.51" y="30.50" width="3.00" height="99.00" fill="hsl(30.3, 82.7%, 50.3%)" rx="1.50" />
|
||||
<rect x="412.51" y="30.35" width="3.00" height="99.30" fill="hsl(30.4, 82.8%, 50.4%)" rx="1.50" />
|
||||
<rect x="416.52" y="29.43" width="3.00" height="101.14" fill="hsl(30.5, 82.8%, 50.5%)" rx="1.50" />
|
||||
<rect x="420.52" y="27.20" width="3.00" height="105.60" fill="hsl(30.6, 82.9%, 50.6%)" rx="1.50" />
|
||||
<rect x="424.53" y="25.42" width="3.00" height="109.16" fill="hsl(30.7, 83.0%, 50.7%)" rx="1.50" />
|
||||
<rect x="428.53" y="24.78" width="3.00" height="110.44" fill="hsl(30.8, 83.1%, 50.8%)" rx="1.50" />
|
||||
<rect x="432.54" y="24.98" width="3.00" height="110.04" fill="hsl(30.9, 83.1%, 50.9%)" rx="1.50" />
|
||||
<rect x="436.55" y="25.12" width="3.00" height="109.76" fill="hsl(31.0, 83.2%, 51.0%)" rx="1.50" />
|
||||
<rect x="440.55" y="24.08" width="3.00" height="111.83" fill="hsl(31.1, 83.3%, 51.1%)" rx="1.50" />
|
||||
<rect x="444.56" y="23.88" width="3.00" height="112.25" fill="hsl(31.2, 83.4%, 51.2%)" rx="1.50" />
|
||||
<rect x="448.56" y="24.96" width="3.00" height="110.08" fill="hsl(31.3, 83.4%, 51.3%)" rx="1.50" />
|
||||
<rect x="452.56" y="23.48" width="3.00" height="113.03" fill="hsl(31.4, 83.5%, 51.4%)" rx="1.50" />
|
||||
<rect x="456.57" y="19.98" width="3.00" height="120.03" fill="hsl(31.5, 83.6%, 51.5%)" rx="1.50" />
|
||||
<rect x="460.57" y="18.14" width="3.00" height="123.73" fill="hsl(31.6, 83.7%, 51.6%)" rx="1.50" />
|
||||
<rect x="464.58" y="18.50" width="3.00" height="122.99" fill="hsl(31.7, 83.7%, 51.7%)" rx="1.50" />
|
||||
<rect x="468.58" y="21.84" width="3.00" height="116.32" fill="hsl(31.8, 83.8%, 51.8%)" rx="1.50" />
|
||||
<rect x="472.59" y="21.66" width="3.00" height="116.67" fill="hsl(31.9, 83.9%, 51.9%)" rx="1.50" />
|
||||
<rect x="476.59" y="16.53" width="3.00" height="126.94" fill="hsl(32.0, 84.0%, 52.0%)" rx="1.50" />
|
||||
<rect x="480.60" y="14.84" width="3.00" height="130.31" fill="hsl(32.1, 84.0%, 52.1%)" rx="1.50" />
|
||||
<rect x="484.60" y="14.65" width="3.00" height="130.71" fill="hsl(32.2, 84.1%, 52.2%)" rx="1.50" />
|
||||
<rect x="488.61" y="13.29" width="3.00" height="133.43" fill="hsl(32.3, 84.2%, 52.3%)" rx="1.50" />
|
||||
<rect x="492.62" y="14.43" width="3.00" height="131.13" fill="hsl(32.4, 84.3%, 52.4%)" rx="1.50" />
|
||||
<rect x="496.62" y="18.37" width="3.00" height="123.27" fill="hsl(32.5, 84.3%, 52.5%)" rx="1.50" />
|
||||
<rect x="500.62" y="20.84" width="3.00" height="118.33" fill="hsl(32.6, 84.4%, 52.6%)" rx="1.50" />
|
||||
<rect x="504.63" y="19.64" width="3.00" height="120.73" fill="hsl(32.7, 84.5%, 52.7%)" rx="1.50" />
|
||||
<rect x="508.63" y="15.79" width="3.00" height="128.41" fill="hsl(32.8, 84.6%, 52.8%)" rx="1.50" />
|
||||
<rect x="512.64" y="13.68" width="3.00" height="132.64" fill="hsl(32.9, 84.6%, 52.9%)" rx="1.50" />
|
||||
<rect x="516.64" y="13.98" width="3.00" height="132.04" fill="hsl(33.0, 84.7%, 53.0%)" rx="1.50" />
|
||||
<rect x="520.65" y="13.55" width="3.00" height="132.90" fill="hsl(33.1, 84.8%, 53.1%)" rx="1.50" />
|
||||
<rect x="524.65" y="14.34" width="3.00" height="131.32" fill="hsl(33.2, 84.9%, 53.2%)" rx="1.50" />
|
||||
<rect x="528.66" y="16.25" width="3.00" height="127.49" fill="hsl(33.3, 84.9%, 53.3%)" rx="1.50" />
|
||||
<rect x="532.66" y="17.08" width="3.00" height="125.84" fill="hsl(33.4, 85.0%, 53.4%)" rx="1.50" />
|
||||
<rect x="536.67" y="16.02" width="3.00" height="127.95" fill="hsl(33.5, 85.1%, 53.5%)" rx="1.50" />
|
||||
<rect x="540.67" y="13.83" width="3.00" height="132.33" fill="hsl(33.6, 85.2%, 53.6%)" rx="1.50" />
|
||||
<rect x="544.68" y="11.14" width="3.00" height="137.73" fill="hsl(33.7, 85.3%, 53.7%)" rx="1.50" />
|
||||
<rect x="548.68" y="13.76" width="3.00" height="132.49" fill="hsl(33.8, 85.3%, 53.8%)" rx="1.50" />
|
||||
<rect x="552.69" y="18.71" width="3.00" height="122.59" fill="hsl(33.9, 85.4%, 53.9%)" rx="1.50" />
|
||||
<rect x="556.69" y="17.31" width="3.00" height="125.38" fill="hsl(34.0, 85.5%, 54.0%)" rx="1.50" />
|
||||
<rect x="560.70" y="12.91" width="3.00" height="134.18" fill="hsl(34.1, 85.6%, 54.1%)" rx="1.50" />
|
||||
<rect x="564.71" y="11.92" width="3.00" height="136.15" fill="hsl(34.2, 85.6%, 54.2%)" rx="1.50" />
|
||||
<rect x="568.71" y="16.93" width="3.00" height="126.13" fill="hsl(34.3, 85.7%, 54.3%)" rx="1.50" />
|
||||
<rect x="572.72" y="22.67" width="3.00" height="114.66" fill="hsl(34.4, 85.8%, 54.4%)" rx="1.50" />
|
||||
<rect x="576.72" y="24.08" width="3.00" height="111.84" fill="hsl(34.5, 85.9%, 54.5%)" rx="1.50" />
|
||||
<rect x="580.73" y="17.60" width="3.00" height="124.80" fill="hsl(34.6, 85.9%, 54.6%)" rx="1.50" />
|
||||
<rect x="584.73" y="10.98" width="3.00" height="138.05" fill="hsl(34.7, 86.0%, 54.7%)" rx="1.50" />
|
||||
<rect x="588.74" y="11.37" width="3.00" height="137.25" fill="hsl(34.8, 86.1%, 54.8%)" rx="1.50" />
|
||||
<rect x="592.74" y="13.18" width="3.00" height="133.64" fill="hsl(34.9, 86.2%, 54.9%)" rx="1.50" />
|
||||
<rect x="596.75" y="12.94" width="3.00" height="134.12" fill="hsl(35.0, 86.2%, 55.0%)" rx="1.50" />
|
||||
<rect x="600.75" y="11.94" width="3.00" height="136.12" fill="hsl(35.1, 86.3%, 55.1%)" rx="1.50" />
|
||||
<rect x="604.75" y="12.03" width="3.00" height="135.94" fill="hsl(35.2, 86.4%, 55.2%)" rx="1.50" />
|
||||
<rect x="608.76" y="11.61" width="3.00" height="136.78" fill="hsl(35.3, 86.5%, 55.3%)" rx="1.50" />
|
||||
<rect x="612.76" y="11.26" width="3.00" height="137.49" fill="hsl(35.4, 86.5%, 55.4%)" rx="1.50" />
|
||||
<rect x="616.77" y="11.99" width="3.00" height="136.03" fill="hsl(35.5, 86.6%, 55.5%)" rx="1.50" />
|
||||
<rect x="620.77" y="15.21" width="3.00" height="129.58" fill="hsl(35.6, 86.7%, 55.6%)" rx="1.50" />
|
||||
<rect x="624.78" y="18.27" width="3.00" height="123.47" fill="hsl(35.7, 86.8%, 55.7%)" rx="1.50" />
|
||||
<rect x="628.78" y="16.53" width="3.00" height="126.93" fill="hsl(35.8, 86.8%, 55.8%)" rx="1.50" />
|
||||
<rect x="632.79" y="12.99" width="3.00" height="134.03" fill="hsl(35.9, 86.9%, 55.9%)" rx="1.50" />
|
||||
<rect x="636.79" y="12.31" width="3.00" height="135.38" fill="hsl(36.0, 87.0%, 56.0%)" rx="1.50" />
|
||||
<rect x="640.80" y="13.73" width="3.00" height="132.54" fill="hsl(36.1, 87.1%, 56.1%)" rx="1.50" />
|
||||
<rect x="644.80" y="15.29" width="3.00" height="129.42" fill="hsl(36.2, 87.1%, 56.2%)" rx="1.50" />
|
||||
<rect x="648.81" y="18.11" width="3.00" height="123.77" fill="hsl(36.3, 87.2%, 56.3%)" rx="1.50" />
|
||||
<rect x="652.81" y="23.90" width="3.00" height="112.20" fill="hsl(36.4, 87.3%, 56.4%)" rx="1.50" />
|
||||
<rect x="656.82" y="32.77" width="3.00" height="94.45" fill="hsl(36.5, 87.4%, 56.5%)" rx="1.50" />
|
||||
<rect x="660.82" y="39.68" width="3.00" height="80.64" fill="hsl(36.6, 87.4%, 56.6%)" rx="1.50" />
|
||||
<rect x="664.83" y="42.98" width="3.00" height="74.05" fill="hsl(36.7, 87.5%, 56.7%)" rx="1.50" />
|
||||
<rect x="668.84" y="47.17" width="3.00" height="65.66" fill="hsl(36.8, 87.6%, 56.8%)" rx="1.50" />
|
||||
<rect x="672.84" y="54.62" width="3.00" height="50.76" fill="hsl(36.9, 87.7%, 56.9%)" rx="1.50" />
|
||||
<rect x="676.85" y="60.98" width="3.00" height="38.05" fill="hsl(37.0, 87.7%, 57.0%)" rx="1.50" />
|
||||
<rect x="680.85" y="60.13" width="3.00" height="39.74" fill="hsl(37.1, 87.8%, 57.1%)" rx="1.50" />
|
||||
<rect x="684.86" y="54.93" width="3.00" height="50.14" fill="hsl(37.2, 87.9%, 57.2%)" rx="1.50" />
|
||||
<rect x="688.86" y="55.59" width="3.00" height="48.82" fill="hsl(37.3, 88.0%, 57.3%)" rx="1.50" />
|
||||
<rect x="692.87" y="63.65" width="3.00" height="32.71" fill="hsl(37.4, 88.0%, 57.4%)" rx="1.50" />
|
||||
<rect x="696.87" y="67.24" width="3.00" height="25.52" fill="hsl(37.5, 88.1%, 57.5%)" rx="1.50" />
|
||||
<rect x="700.88" y="61.51" width="3.00" height="36.99" fill="hsl(37.6, 88.2%, 57.6%)" rx="1.50" />
|
||||
<rect x="704.88" y="56.91" width="3.00" height="46.19" fill="hsl(37.7, 88.3%, 57.7%)" rx="1.50" />
|
||||
<rect x="708.88" y="59.62" width="3.00" height="40.77" fill="hsl(37.8, 88.3%, 57.8%)" rx="1.50" />
|
||||
<rect x="712.89" y="62.17" width="3.00" height="35.65" fill="hsl(37.9, 88.4%, 57.9%)" rx="1.50" />
|
||||
<rect x="716.89" y="61.00" width="3.00" height="38.01" fill="hsl(38.0, 88.5%, 58.0%)" rx="1.50" />
|
||||
<rect x="720.90" y="59.04" width="3.00" height="41.92" fill="hsl(38.1, 88.6%, 58.1%)" rx="1.50" />
|
||||
<rect x="724.90" y="58.23" width="3.00" height="43.55" fill="hsl(38.2, 88.6%, 58.2%)" rx="1.50" />
|
||||
<rect x="728.91" y="60.97" width="3.00" height="38.05" fill="hsl(38.3, 88.7%, 58.3%)" rx="1.50" />
|
||||
<rect x="732.91" y="63.99" width="3.00" height="32.02" fill="hsl(38.4, 88.8%, 58.4%)" rx="1.50" />
|
||||
<rect x="736.92" y="63.07" width="3.00" height="33.86" fill="hsl(38.5, 88.9%, 58.5%)" rx="1.50" />
|
||||
<rect x="740.92" y="60.54" width="3.00" height="38.92" fill="hsl(38.6, 88.9%, 58.6%)" rx="1.50" />
|
||||
<rect x="744.93" y="56.58" width="3.00" height="46.83" fill="hsl(38.7, 89.0%, 58.7%)" rx="1.50" />
|
||||
<rect x="748.93" y="55.84" width="3.00" height="48.33" fill="hsl(38.8, 89.1%, 58.8%)" rx="1.50" />
|
||||
<rect x="752.94" y="60.72" width="3.00" height="38.56" fill="hsl(38.9, 89.2%, 58.9%)" rx="1.50" />
|
||||
<rect x="756.94" y="61.75" width="3.00" height="36.50" fill="hsl(39.0, 89.2%, 59.0%)" rx="1.50" />
|
||||
<rect x="760.95" y="62.25" width="3.00" height="35.50" fill="hsl(39.1, 89.3%, 59.1%)" rx="1.50" />
|
||||
<rect x="764.95" y="67.65" width="3.00" height="24.70" fill="hsl(39.2, 89.4%, 59.2%)" rx="1.50" />
|
||||
<rect x="768.96" y="71.99" width="3.00" height="16.02" fill="hsl(39.3, 89.5%, 59.3%)" rx="1.50" />
|
||||
<rect x="772.97" y="74.43" width="3.00" height="11.14" fill="hsl(39.4, 89.5%, 59.4%)" rx="1.50" />
|
||||
<rect x="776.97" y="76.03" width="3.00" height="7.94" fill="hsl(39.5, 89.6%, 59.5%)" rx="1.50" />
|
||||
<rect x="780.98" y="76.65" width="3.00" height="6.70" fill="hsl(39.6, 89.7%, 59.6%)" rx="1.50" />
|
||||
<rect x="784.98" y="77.05" width="3.00" height="5.90" fill="hsl(39.7, 89.8%, 59.7%)" rx="1.50" />
|
||||
<rect x="788.99" y="77.37" width="3.00" height="5.25" fill="hsl(39.8, 89.8%, 59.8%)" rx="1.50" />
|
||||
<rect x="792.99" y="77.61" width="3.00" height="4.79" fill="hsl(39.9, 89.9%, 59.9%)" rx="1.50" />
|
||||
<rect x="797.00" y="77.68" width="3.00" height="4.63" fill="hsl(40.0, 90.0%, 60.0%)" rx="1.50" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 20 KiB |
+48
-5
@@ -3,6 +3,7 @@ import Generator from './components/Generator'
|
||||
import Gallery from './components/Gallery'
|
||||
import Player from './components/Player'
|
||||
import DetailPanel from './components/DetailPanel'
|
||||
import SettingsModal from './components/SettingsModal'
|
||||
|
||||
interface Clip {
|
||||
id: string
|
||||
@@ -22,6 +23,7 @@ function App() {
|
||||
const [currentClip, setCurrentClip] = useState<Clip | null>(null)
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
const [selectedClip, setSelectedClip] = useState<Clip | null>(null)
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
|
||||
const fetchClips = async () => {
|
||||
try {
|
||||
@@ -117,15 +119,48 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearWaveforms = async () => {
|
||||
try {
|
||||
await fetch('/api/admin/clear-waveforms', { method: 'POST' })
|
||||
} catch (err) {
|
||||
console.error('Failed to clear waveforms:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAll = async () => {
|
||||
try {
|
||||
await fetch('/api/admin/delete-all-clips', { method: 'POST' })
|
||||
setClips([])
|
||||
setCurrentClip(null)
|
||||
setIsPlaying(false)
|
||||
setSelectedClip(null)
|
||||
} catch (err) {
|
||||
console.error('Failed to delete all clips:', err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-lyria-dark">
|
||||
<header className="border-b border-white/10 px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded bg-lyria-accent flex items-center justify-center text-white font-bold text-sm">
|
||||
L
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded bg-lyria-accent flex items-center justify-center text-white font-bold text-sm">
|
||||
L
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-white">Lyria Studio</h1>
|
||||
<span className="text-xs text-lyria-muted bg-white/5 px-2 py-0.5 rounded">v0.1</span>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-white">Lyria Studio</h1>
|
||||
<span className="text-xs text-lyria-muted bg-white/5 px-2 py-0.5 rounded">v0.1</span>
|
||||
<button
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
className="text-lyria-muted hover:text-white transition-colors p-1 rounded hover:bg-white/5"
|
||||
title="Settings"
|
||||
aria-label="Open settings"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -168,6 +203,14 @@ function App() {
|
||||
onRemoveArtwork={handleRemoveArtwork}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SettingsModal
|
||||
isOpen={settingsOpen}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
onClearWaveforms={handleClearWaveforms}
|
||||
onDeleteAll={handleDeleteAll}
|
||||
clipCount={clips.length}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onClearWaveforms: () => Promise<void>
|
||||
onDeleteAll: () => Promise<void>
|
||||
clipCount: number
|
||||
}
|
||||
|
||||
export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDeleteAll, clipCount }: Props) {
|
||||
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative bg-lyria-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<h2 className="text-lg font-semibold text-white">Settings</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-lyria-muted hover:text-white transition-colors"
|
||||
aria-label="Close settings"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-6 py-5 space-y-6">
|
||||
{/* Danger Zone */}
|
||||
<div className="border border-red-500/30 rounded-lg overflow-hidden">
|
||||
<div className="bg-red-500/10 px-4 py-2 border-b border-red-500/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-red-400">Danger Zone</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 space-y-4">
|
||||
{/* Clear Waveforms */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-white font-medium">Clear Waveform Cache</p>
|
||||
<p className="text-xs text-lyria-muted mt-0.5">Delete all cached SVGs. They regenerate on next view.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClearWaveforms}
|
||||
disabled={loading}
|
||||
className="px-3 py-1.5 text-sm rounded bg-white/5 text-white hover:bg-white/10 disabled:opacity-50 transition-colors border border-white/10"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr className="border-white/5" />
|
||||
|
||||
{/* Delete All */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-white font-medium">Delete All Tracks</p>
|
||||
<p className="text-xs text-lyria-muted mt-0.5">
|
||||
Remove {clipCount} clip{clipCount !== 1 ? 's' : ''}, MP3s, artwork, and waveforms. Cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleDeleteAll}
|
||||
disabled={loading || clipCount === 0}
|
||||
className={`px-3 py-1.5 text-sm rounded transition-colors border ${
|
||||
confirmDelete
|
||||
? 'bg-red-500 text-white hover:bg-red-600 border-red-500'
|
||||
: 'bg-white/5 text-red-400 hover:bg-red-500/10 border-red-500/30'
|
||||
} disabled:opacity-30`}
|
||||
>
|
||||
{confirmDelete ? 'Confirm Delete' : 'Delete All'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{confirmDelete && (
|
||||
<p className="text-xs text-red-400 mt-2">
|
||||
Click again to confirm. This permanently deletes everything.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user