From 78426c00ddfed65f96bdc323b4a70d766c6febca Mon Sep 17 00:00:00 2001 From: fspecii <4722521+fspecii@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:06:44 +0200 Subject: [PATCH] Fix LoRA training Error 500 - remove Radio serialization issue The @gradio/client was wrapping Radio component values as {"value": "replace", "__type__": "update"} instead of plain strings, causing the update_settings Gradio call to fail and crash the server. - /update-settings: removed Gradio call, settings are applied at save time - /save-dataset: migrated to REST API (/v1/dataset/save) which accepts tag_position and other settings directly as strings - Frontend: pass dataset settings (tag, position, etc.) when saving --- components/TrainingPanel.tsx | 4 +++ server/src/routes/training.ts | 58 +++++++++++++++++++---------------- services/api.ts | 4 +++ 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/components/TrainingPanel.tsx b/components/TrainingPanel.tsx index 362d096..00e0445 100644 --- a/components/TrainingPanel.tsx +++ b/components/TrainingPanel.tsx @@ -474,6 +474,10 @@ export const TrainingPanel: React.FC = () => { const result = await trainingApi.saveDataset({ savePath: savePath || `./datasets/${datasetSettings.datasetName}.json`, datasetName: datasetSettings.datasetName, + customTag: datasetSettings.customTag, + tagPosition: datasetSettings.tagPosition, + allInstrumental: datasetSettings.allInstrumental, + genreRatio: datasetSettings.genreRatio, }, token); setSaveStatus(result.status as string); if (result.path) setSavePath(result.path); diff --git a/server/src/routes/training.ts b/server/src/routes/training.ts index 07c3e5d..a6f0299 100644 --- a/server/src/routes/training.ts +++ b/server/src/routes/training.ts @@ -717,41 +717,45 @@ router.post('/save-sample', authMiddleware, async (req: AuthenticatedRequest, re }); // POST /api/training/update-settings — Update dataset global settings -router.post('/update-settings', authMiddleware, async (req: AuthenticatedRequest, res: Response) => { - try { - const { customTag, tagPosition, allInstrumental, genreRatio } = req.body; - - const client = await getGradioClient(); - await client.predict('/update_settings', [ - customTag ?? '', - tagPosition ?? 'replace', - allInstrumental ?? true, - genreRatio ?? 0, - ]); - - res.json({ success: true }); - } catch (error) { - console.error('[Training] Update settings error:', error); - res.status(500).json({ error: error instanceof Error ? error.message : 'Failed to update settings' }); - } +// Settings are applied directly when saving (via REST API), so no Gradio call needed here. +router.post('/update-settings', authMiddleware, (_req: AuthenticatedRequest, res: Response) => { + res.json({ success: true }); }); // POST /api/training/save-dataset — Save the dataset to a JSON file router.post('/save-dataset', authMiddleware, async (req: AuthenticatedRequest, res: Response) => { try { - const { savePath, datasetName } = req.body; + const { savePath, datasetName, customTag, tagPosition, allInstrumental, genreRatio } = req.body; - const client = await getGradioClient(); - const result = await client.predict('/save_dataset', [ - savePath ?? './datasets/my_lora_dataset.json', - datasetName ?? 'my_lora_dataset', - ]); - const data = result.data as unknown[]; + const resolvedPath = (savePath ?? `./datasets/${datasetName ?? 'my_lora_dataset'}.json`).trim(); - // Returns: [saveStatus, savePath] + // Use REST API to avoid @gradio/client Radio serialization issues + const apiUrl = config.acestep.apiUrl; + const body: Record = { + save_path: resolvedPath, + dataset_name: datasetName ?? 'my_lora_dataset', + }; + if (customTag !== undefined) body.custom_tag = customTag; + if (tagPosition !== undefined) body.tag_position = tagPosition; + if (allInstrumental !== undefined) body.all_instrumental = allInstrumental; + if (genreRatio !== undefined) body.genre_ratio = genreRatio; + + const apiRes = await fetch(`${apiUrl}/v1/dataset/save`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(30_000), + }); + + if (!apiRes.ok) { + const err = await apiRes.json().catch(() => ({})) as any; + throw new Error(err?.detail || err?.error || `Save failed: ${apiRes.status}`); + } + + const data = await apiRes.json() as any; res.json({ - status: data[0], - path: data[1], + status: data.status ?? 'Saved', + path: data.save_path ?? resolvedPath, }); } catch (error) { console.error('[Training] Save dataset error:', error); diff --git a/services/api.ts b/services/api.ts index 0e8e0de..6d1150c 100644 --- a/services/api.ts +++ b/services/api.ts @@ -768,6 +768,10 @@ export const trainingApi = { saveDataset: (params: { savePath?: string; datasetName?: string; + customTag?: string; + tagPosition?: string; + allInstrumental?: boolean; + genreRatio?: number; }, token: string): Promise<{ status: string; path: string }> => api('/api/training/save-dataset', { method: 'POST', body: params, token }),