Fix multiple issues: format API, Gradio availability, storage, UI responsiveness

- Format endpoint now calls ACE-Step /format_input REST API directly instead of
  spawning Python, fixing ENOENT errors on Windows (#44, #27, #34)
- isGradioAvailable() tries /gradio_api/info, /info, / in sequence to handle
  Gradio 4.x/5.x/6.x version differences, fixing generation fallback (#53, #20)
- Storage getUrl/getPublicUrl normalize /audio/ prefix to prevent double-prefix
  URLs when reference tracks are used for cover generation (#10)
- Gradio args: fix normalization_db default from 0.0 to -1.0 (Gradio default)
- Volume popover: add 400ms delay before hiding to prevent accidental dismissal (#51)
- Polling: skip setSongs state update when nothing changed to reduce re-renders (#51)
- FFmpeg: add jsdelivr CDN fallback when unpkg fails (#30)
- Model switching: add switchModelIfNeeded() via /v1/init REST API (#45)
This commit is contained in:
fspecii
2026-03-02 17:43:53 +02:00
parent 553e0bce2f
commit e1625a717d
7 changed files with 208 additions and 63 deletions
+53 -13
View File
@@ -77,7 +77,7 @@ const SCRIPTS_DIR = path.join(__dirname, '../../scripts');
const PYTHON_SCRIPT = path.join(SCRIPTS_DIR, 'simple_generate.py');
// ---------------------------------------------------------------------------
// Gradio generation: map params to the 45 positional args for /generation_wrapper
// Gradio generation: map params to the 51 positional args for /generation_wrapper
// ---------------------------------------------------------------------------
/**
@@ -186,16 +186,16 @@ async function buildGradioArgs(params: GenerationParams): Promise<unknown[]> {
params.constrainedDecodingDebug ?? false, // 38: Constrained Decoding Debug
params.allowLmBatch ?? true, // 39: ParallelThinking
params.getScores ?? false, // 40: Auto Score
// Note: auto_lrc (getLrc) is a hidden Gradio state param — NOT included in the public API args
params.scoreScale ?? 0.5, // 41: Quality Score Sensitivity (0.01-1.0)
params.lmBatchChunkSize ?? 8, // 42: LM Batch Chunk Size
params.trackName || null, // 43: Track Name
params.completeTrackClasses || [], // 44: Track Names
false, // 45: Enable Normalization (ACE-Step v1.5 new param, default false)
0.0, // 46: Normalization DB (ACE-Step v1.5 new param, default 0.0)
0.0, // 47: Latent Shift (ACE-Step v1.5 new param, default 0.0)
1.0, // 48: Latent Rescale (ACE-Step v1.5 new param, default 1.0)
params.autogen ?? false, // 49: AutoGen (visible last param)
params.getLrc ?? false, // 41: Auto LRC (timestamped lyrics)
params.scoreScale ?? 0.5, // 42: Quality Score Sensitivity (0.01-1.0)
params.lmBatchChunkSize ?? 8, // 43: LM Batch Chunk Size
params.trackName || null, // 44: Track Name
params.completeTrackClasses || [], // 45: Track Names
true, // 46: Enable Normalization (ACE-Step v1.5, default true)
-1.0, // 47: Normalization DB (ACE-Step v1.5, default -1.0)
0.0, // 48: Latent Shift (ACE-Step v1.5, default 0.0)
1.0, // 49: Latent Rescale (ACE-Step v1.5, default 1.0)
params.autogen ?? false, // 50: AutoGen
// Note: current_batch_index, total_batches, batch_queue, generation_params_state
// are hidden Gradio state variables and must NOT be passed via client.predict()
];
@@ -365,6 +365,40 @@ export async function checkSpaceHealth(): Promise<boolean> {
return isGradioAvailable();
}
// ---------------------------------------------------------------------------
// Model switching — call /v1/init to change the active DiT model
// ---------------------------------------------------------------------------
async function getActiveModel(): Promise<string | null> {
try {
const res = await fetch(`${ACESTEP_API}/v1/models`);
if (!res.ok) return null;
const data = await res.json() as any;
const models = data?.data?.models || data?.models || [];
return models[0]?.name || null;
} catch {
return null;
}
}
async function switchModelIfNeeded(ditModel: string): Promise<void> {
const activeModel = await getActiveModel();
if (activeModel === ditModel) return; // already loaded, no-op
console.log(`[Model] Switching from '${activeModel ?? 'unknown'}' to '${ditModel}'`);
const res = await fetch(`${ACESTEP_API}/v1/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: ditModel, init_llm: false }),
});
if (!res.ok) {
const err = await res.text().catch(() => '');
throw new Error(`Model switch to '${ditModel}' failed: ${res.status} ${err}`);
}
console.log(`[Model] Switched to '${ditModel}'`);
}
// Discover endpoints (for compatibility)
export async function discoverEndpoints(): Promise<unknown> {
return { provider: 'acestep-gradio', endpoint: ACESTEP_API };
@@ -472,6 +506,12 @@ async function processGenerationViaGradio(
params: GenerationParams,
job: ActiveJob,
): Promise<void> {
// Switch DiT model if a specific one was requested
if (params.ditModel) {
job.stage = `Loading model ${params.ditModel}...`;
await switchModelIfNeeded(params.ditModel);
}
const client = await getGradioClient();
const args = await buildGradioArgs(params);
@@ -552,7 +592,7 @@ async function processGenerationViaGradio(
const finalDuration = actualDuration > 0
? actualDuration
: (metas.duration || params.duration || 60);
: (metas.duration || params.duration || 0);
job.status = 'succeeded';
job.result = {
@@ -703,7 +743,7 @@ async function processGenerationViaPython(
console.warn(`Job ${jobId}: Failed to cleanup output dir`, cleanupError);
}
const finalDuration = actualDuration > 0 ? actualDuration : (params.duration && params.duration > 0 ? params.duration : 60);
const finalDuration = actualDuration > 0 ? actualDuration : (params.duration && params.duration > 0 ? params.duration : 0);
job.status = 'succeeded';
job.result = {