UX - Add elapsed-time counter and Cancel button to in-flight generations, wired through AbortController so partial responses are discarded cleanly. The cancel action is available from both the input panel and the results header. - Add a Recent generations panel below the input. The last 6 successful generations are saved to localStorage; one click reloads both the input fields and the generated assets. - Add a Revert button to every editable card. It appears the moment the current value diverges from the last generated value and restores the field with a single click. - Add an Empty state to the right panel with a friendly hint pointing at the Generate button and the Settings page. - Add a 'Try an example' button that fills the input with a random starter idea (idea + mood + vocals). - Add Cmd/Ctrl+Enter as a keyboard shortcut to generate. - Add a Footer with project info and a privacy reminder. - Add a 'Clear saved key' button to the Settings page so the user can remove the API key without overwriting it. Bug fix - Settings > Test Connection used to save the in-progress form values to localStorage before testing. It now uses the in-memory candidate config, so failed tests don't pollute the saved config. Code quality - Extract the duplicated useAutoHeight hook to src/lib/useAutoHeight.ts. - Extract a useElapsed hook for the loading timer. - Move InputValues into src/lib/types.ts (was duplicated in InputPanel.tsx) and add SECTION_LABELS, replacing the humanizeSection switch in HomePage. - Centralize the filename sanitization: HomePage now calls sanitizeFilename from zip.ts instead of duplicating the regex. - Add previewConfig / testConnectionWithConfig helpers to llm.ts to support in-memory connection tests.
5.6 KiB
MelodyMuse
Generate all assets needed for a Suno AI song from a free-text description — titles, lyrics, style prompt, video prompts (Abstract / Cinematic / Hybrid), and a YouTube description, then download everything as a ZIP.
The app runs entirely in your browser as a single static SPA. There is no
backend, no database, no third-party services: the only network calls are
direct fetch requests from your browser to the AI provider's
OpenAI-compatible /chat/completions endpoint, using credentials that you
paste into the Settings page. Those credentials are kept in this browser's
localStorage.
Features
- 🎵 All 5 Suno assets in one click — titles, lyrics, style, video prompts, YouTube description
- ✏️ Inline editing — every generated field is editable; revert any edit with one click
- 🔄 Per-section regeneration — regenerate just one section; the rest of the song is passed as context for consistency
- ⏱ Elapsed-time counter + ❌ Cancel button for long generations
- 🕘 Recent generations — last 6 are kept in localStorage; one click to reload
- ⌨️ Keyboard shortcut —
Cmd/Ctrl + Enterto generate - 🎲 Try an example — fill the input with a random sample idea
- 🌗 Dark / light mode — toggle in the header, persisted
- 💾 Standalone — no backend, no signup, no telemetry
Tech stack
- React 18 + TypeScript + Vite
- Tailwind CSS (dark by default, light theme via
html.light) - JSZip for client-side ZIP generation
react-router-domfor the two routes (/,/settings)
Repository layout
MelodyMuse/
├── src/
│ ├── App.tsx
│ ├── main.tsx
│ ├── index.css # Tailwind layers + design-system components
│ ├── components/ # UI building blocks
│ │ ├── cards/ # The five result cards
│ │ ├── ConfigBanner.tsx
│ │ ├── CopyButton.tsx
│ │ ├── EqualizerIcon.tsx
│ │ ├── Footer.tsx
│ │ ├── HistoryPanel.tsx
│ │ ├── InputPanel.tsx
│ │ ├── ResultCard.tsx
│ │ ├── ResultsPanel.tsx
│ │ ├── SkeletonCard.tsx
│ │ ├── StickyZipBar.tsx
│ │ └── ThemeToggle.tsx
│ ├── pages/ # HomePage, SettingsPage
│ ├── lib/
│ │ ├── history.ts # recent-generations persistence
│ │ ├── llm.ts # direct fetch → provider, JSON extraction, config persistence
│ │ ├── prompts.ts # system + user prompt construction
│ │ ├── theme.ts # dark/light mode
│ │ ├── toast.tsx # toast context
│ │ ├── types.ts # shared TypeScript types + SECTION_LABELS
│ │ ├── useAutoHeight.ts # shared textarea auto-grow hook
│ │ ├── useElapsed.ts # shared "X seconds elapsed" hook
│ │ └── zip.ts # JSZip layout
│ └── vite-env.d.ts
├── public/favicon.svg
├── index.html
├── tailwind.config.js
├── vite.config.ts
├── tsconfig*.json
└── package.json
Getting started
-
Install dependencies
npm install -
Start the dev server
npm run devOpen http://localhost:5173 in your browser.
-
Open Settings and fill in:
- API Endpoint URL — e.g.
https://api.minimax.chat/v1 - API Key — your provider's secret key
- Model Name — e.g.
MiniMax-M3
Click Save Configuration, then Test Connection to confirm everything is wired up. Test Connection uses your unsaved form values — your changes are only persisted when you click Save.
- API Endpoint URL — e.g.
-
Generate. Return to the home page, type a music idea, click Generate Song Assets (or press
Cmd/Ctrl + Enter).
Build for production
npm run build
Outputs static assets in dist/. The dist/ folder is a normal SPA — serve
it from any static host (GitHub Pages, Netlify, Vercel, python -m http.server,
…).
⚠️ Heads up about deployment. Because the API key is stored in the browser's
localStorage, you should not serve a public deployment of this app and use it with a real key on a shared device. For personal/local use this is fine.
CORS
The app makes direct cross-origin requests from the browser to your
provider. If your provider does not send the right Access-Control-Allow-*
headers for your origin, the request will fail with a CORS error. The
generated error message will explicitly call this out. Workarounds:
- Pick a provider/endpoint that already permits browser CORS (most managed OpenAI-compatible services do).
- Run the app on the same origin as the API (i.e. front it with a tiny proxy).
- Use a CORS-permissive browser extension during local development.
Provider format
The configured endpoint must expose an OpenAI-compatible
POST {api_endpoint}/chat/completions route that accepts
{ model, messages, max_tokens } and returns
{ choices: [{ message: { content } }] }.
Security notes
- The API key is held in
localStorageand is only sent to the endpoint you configure. No analytics, no telemetry, no third-party calls. - Three localStorage keys are used:
melodymuse-config—{ api_endpoint, api_key, model_name }melodymuse-history— the last 6 generations (input + assets)melodymuse-theme—'dark' | 'light'
- You can clear them at any time from your browser's devtools (Application → Local Storage).
License
Personal use. Do whatever you want with it.