Spaces:
Running
Running
File size: 9,504 Bytes
bc9c638 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ACE-Step Studio (Experimental)</title>
<style>
:root { --bg: #1a1a2e; --card: #16213e; --accent: #0f3460; --text: #e8e8e8; --muted: #a0a0a0; }
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 1rem; }
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
.badge { font-size: 0.7rem; background: var(--accent); padding: 0.2rem 0.5rem; border-radius: 4px; margin-left: 0.5rem; }
section { background: var(--card); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; }
section h2 { font-size: 0.9rem; margin: 0 0 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
label { display: block; margin-bottom: 0.25rem; font-size: 0.85rem; }
input[type="text"], input[type="number"], input[type="url"], textarea { width: 100%; padding: 0.5rem; border: 1px solid var(--accent); border-radius: 4px; background: var(--bg); color: var(--text); font-size: 0.9rem; }
textarea { min-height: 80px; resize: vertical; }
.row { display: flex; gap: 1rem; flex-wrap: wrap; }
.row > * { flex: 1 1 200px; }
button { background: var(--accent); color: var(--text); border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-size: 0.9rem; }
button:hover { filter: brightness(1.1); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.checkbox { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.checkbox input { width: auto; }
#log { font-size: 0.8rem; color: var(--muted); margin-top: 0.5rem; white-space: pre-wrap; max-height: 120px; overflow-y: auto; }
#results { margin-top: 1rem; }
.result-item { margin-bottom: 1rem; padding: 0.75rem; background: var(--bg); border-radius: 4px; }
.result-item audio { width: 100%; margin-top: 0.5rem; }
.result-item .meta { font-size: 0.8rem; color: var(--muted); margin-top: 0.5rem; }
.error { color: #e57373; }
.success { color: #81c784; }
</style>
</head>
<body>
<h1>ACE-Step Studio <span class="badge">Experimental</span></h1>
<p style="color: var(--muted); font-size: 0.9rem; margin: 0 0 1rem;">Optional frontend for the ACE-Step REST API. Start the API server, then open this file in a browser.</p>
<section>
<h2>Connection</h2>
<label for="apiBase">API base URL</label>
<input type="url" id="apiBase" value="http://localhost:8001" placeholder="http://localhost:8001">
</section>
<section>
<h2>Prompt</h2>
<label for="prompt">Music description (prompt)</label>
<input type="text" id="prompt" placeholder="e.g. upbeat pop song with electric guitar">
<label for="lyrics" style="margin-top: 0.5rem;">Lyrics (optional)</label>
<textarea id="lyrics" placeholder="[Verse 1] ..."></textarea>
<div class="checkbox">
<input type="checkbox" id="sampleMode">
<label for="sampleMode" style="margin:0;">Sample mode (generate from description only)</label>
</div>
<div id="sampleQueryRow" style="display: none;">
<label for="sampleQuery">Sample query</label>
<input type="text" id="sampleQuery" placeholder="e.g. a soft Bengali love song">
</div>
</section>
<section>
<h2>Options</h2>
<div class="row">
<div>
<label for="vocalLanguage">Vocal language</label>
<input type="text" id="vocalLanguage" value="en" placeholder="en">
</div>
<div>
<label for="audioDuration">Duration (seconds)</label>
<input type="number" id="audioDuration" min="10" max="600" placeholder="30">
</div>
<div>
<label for="batchSize">Batch size</label>
<input type="number" id="batchSize" value="1" min="1" max="8">
</div>
</div>
<div class="checkbox">
<input type="checkbox" id="thinking" checked>
<label for="thinking" style="margin:0;">Thinking (LM generates codes + metas)</label>
</div>
</section>
<section>
<button id="submitBtn">Generate</button>
<div id="log"></div>
<div id="results"></div>
</section>
<script>
const apiBaseEl = document.getElementById('apiBase');
const promptEl = document.getElementById('prompt');
const lyricsEl = document.getElementById('lyrics');
const sampleModeEl = document.getElementById('sampleMode');
const sampleQueryRow = document.getElementById('sampleQueryRow');
const sampleQueryEl = document.getElementById('sampleQuery');
const vocalLanguageEl = document.getElementById('vocalLanguage');
const audioDurationEl = document.getElementById('audioDuration');
const batchSizeEl = document.getElementById('batchSize');
const thinkingEl = document.getElementById('thinking');
const submitBtn = document.getElementById('submitBtn');
const logEl = document.getElementById('log');
const resultsEl = document.getElementById('results');
sampleModeEl.addEventListener('change', function () {
sampleQueryRow.style.display = this.checked ? 'block' : 'none';
});
function log(msg, type) {
const p = document.createElement('div');
p.className = type || '';
p.textContent = new Date().toLocaleTimeString() + ' ' + msg;
logEl.appendChild(p);
logEl.scrollTop = logEl.scrollHeight;
}
function clearLog() { logEl.innerHTML = ''; }
function clearResults() { resultsEl.innerHTML = ''; }
function getBase() {
let base = (apiBaseEl.value || '').trim().replace(/\/+$/, '');
if (!base) base = 'http://localhost:8001';
return base;
}
async function releaseTask(body) {
const base = getBase();
const res = await fetch(base + '/release_task', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
if (data.code !== 200 || !data.data || !data.data.task_id) {
throw new Error(data.error || data.data?.message || 'Release task failed');
}
return data.data.task_id;
}
async function queryResult(taskIdList) {
const base = getBase();
const res = await fetch(base + '/query_result', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id_list: taskIdList }),
});
const data = await res.json();
if (data.code !== 200) throw new Error(data.error || 'Query failed');
return data.data;
}
function renderResult(taskId, item, base) {
const file = item.file || '';
const url = file.startsWith('http') ? file : base + (file.startsWith('/') ? '' : '/') + file;
const meta = item.metas || {};
const metaStr = [meta.bpm && 'BPM ' + meta.bpm, meta.keyscale, meta.timesignature, meta.duration && meta.duration + 's'].filter(Boolean).join(' · ');
const div = document.createElement('div');
div.className = 'result-item';
div.innerHTML = '<div><strong>' + (item.prompt || '—').replace(/</g, '<') + '</strong></div>' +
(metaStr ? '<div class="meta">' + metaStr + '</div>' : '') +
'<audio controls src="' + url + '"></audio>';
resultsEl.appendChild(div);
}
submitBtn.addEventListener('click', async function () {
clearLog();
clearResults();
submitBtn.disabled = true;
const base = getBase();
const body = {
prompt: promptEl.value.trim() || '',
lyrics: lyricsEl.value.trim() || '',
vocal_language: vocalLanguageEl.value.trim() || 'en',
thinking: thinkingEl.checked,
batch_size: Math.min(8, Math.max(1, parseInt(batchSizeEl.value, 10) || 1)),
};
const dur = parseInt(audioDurationEl.value, 10);
if (dur >= 10 && dur <= 600) body.audio_duration = dur;
if (sampleModeEl.checked) {
body.sample_mode = true;
body.sample_query = sampleQueryEl.value.trim() || '';
}
try {
log('Submitting task...');
const taskId = await releaseTask(body);
log('Task ID: ' + taskId, 'success');
const pollInterval = 1500;
const maxWait = 600000;
const start = Date.now();
let list;
while (Date.now() - start < maxWait) {
await new Promise(function (r) { setTimeout(r, pollInterval); });
list = await queryResult([taskId]);
if (!list || !list.length) { log('No result in response'); continue; }
const item = list[0];
const status = item.status;
if (item.progress_text) log(item.progress_text);
if (status === 1) {
log('Done.', 'success');
try {
const result = typeof item.result === 'string' ? JSON.parse(item.result) : item.result;
const arr = Array.isArray(result) ? result : [result];
arr.forEach(function (r) { renderResult(taskId, r, base); });
} catch (e) {
log('Parse result: ' + e.message, 'error');
}
break;
}
if (status === 2) {
log('Task failed.', 'error');
break;
}
}
if (Date.now() - start >= maxWait) log('Timeout.', 'error');
} catch (e) {
log(e.message || 'Error', 'error');
}
submitBtn.disabled = false;
});
</script>
</body>
</html>
|