diff --git a/qwen3-tts-frontend/src/components/tts/CustomVoiceForm.tsx b/qwen3-tts-frontend/src/components/tts/CustomVoiceForm.tsx index 82071a7..c3a981b 100644 --- a/qwen3-tts-frontend/src/components/tts/CustomVoiceForm.tsx +++ b/qwen3-tts-frontend/src/components/tts/CustomVoiceForm.tsx @@ -160,17 +160,32 @@ const CustomVoiceForm = forwardRef((_props, ref) => { let result if (selectedItem?.source === 'saved-design') { - result = await ttsApi.createVoiceDesignJob({ - text: data.text, - language: data.language, - instruct: selectedItem.instruct!, - saved_design_id: selectedItem.designId, - max_new_tokens: data.max_new_tokens, - temperature: data.temperature, - top_k: data.top_k, - top_p: data.top_p, - repetition_penalty: data.repetition_penalty, - }) + if (selectedItem.backendType === 'local') { + result = await ttsApi.createVoiceCloneJob({ + text: data.text, + language: data.language, + ref_audio: null, + voice_design_id: selectedItem.designId, + max_new_tokens: data.max_new_tokens, + temperature: data.temperature, + top_k: data.top_k, + top_p: data.top_p, + repetition_penalty: data.repetition_penalty, + backend: 'local', + }) + } else { + result = await ttsApi.createVoiceDesignJob({ + text: data.text, + language: data.language, + instruct: selectedItem.instruct!, + saved_design_id: selectedItem.designId, + max_new_tokens: data.max_new_tokens, + temperature: data.temperature, + top_k: data.top_k, + top_p: data.top_p, + repetition_penalty: data.repetition_penalty, + }) + } } else { result = await ttsApi.createCustomVoiceJob(data) } diff --git a/qwen3-tts-frontend/src/components/tts/VoiceDesignForm.tsx b/qwen3-tts-frontend/src/components/tts/VoiceDesignForm.tsx index 85c2e7a..a2f3497 100644 --- a/qwen3-tts-frontend/src/components/tts/VoiceDesignForm.tsx +++ b/qwen3-tts-frontend/src/components/tts/VoiceDesignForm.tsx @@ -52,6 +52,7 @@ const VoiceDesignForm = forwardRef((_props, ref) => { }) const [showSaveDialog, setShowSaveDialog] = useState(false) const [saveDesignName, setSaveDesignName] = useState('') + const [isPreparing, setIsPreparing] = useState(false) const { currentJob, isPolling, isCompleted, startPolling, elapsedTime } = useJobPolling() const { refresh } = useHistoryContext() @@ -128,13 +129,29 @@ const VoiceDesignForm = forwardRef((_props, ref) => { toast.error('请输入设计名称') return } + try { - await voiceDesignApi.create({ + const backend = preferences?.default_backend || 'local' + const design = await voiceDesignApi.create({ name: saveDesignName, instruct: instruct, - backend_type: preferences?.default_backend || 'local' + backend_type: backend }) + toast.success('音色设计已保存') + + if (backend === 'local') { + setIsPreparing(true) + try { + await voiceDesignApi.prepareClone(design.id) + toast.success('音色克隆准备完成') + } catch (error) { + toast.error('准备克隆失败,但设计已保存') + } finally { + setIsPreparing(false) + } + } + setShowSaveDialog(false) setSaveDesignName('') } catch (error) { @@ -238,8 +255,8 @@ const VoiceDesignForm = forwardRef((_props, ref) => { }}> 取消 - diff --git a/qwen3-tts-frontend/src/lib/api.ts b/qwen3-tts-frontend/src/lib/api.ts index c05752b..09d4265 100644 --- a/qwen3-tts-frontend/src/lib/api.ts +++ b/qwen3-tts-frontend/src/lib/api.ts @@ -259,6 +259,9 @@ export const ttsApi = { if (data.x_vector_only_mode !== undefined) { formData.append('x_vector_only_mode', String(data.x_vector_only_mode)) } + if (data.voice_design_id !== undefined) { + formData.append('voice_design_id', String(data.voice_design_id)) + } if (data.max_new_tokens !== undefined) { formData.append('max_new_tokens', String(data.max_new_tokens)) } @@ -422,6 +425,13 @@ export const voiceDesignApi = { delete: async (id: number): Promise => { await apiClient.delete(API_ENDPOINTS.VOICE_DESIGNS.DELETE(id)) }, + + prepareClone: async (id: number): Promise<{ message: string; cache_id: number; ref_text: string }> => { + const response = await apiClient.post<{ message: string; cache_id: number; ref_text: string }>( + API_ENDPOINTS.VOICE_DESIGNS.PREPARE_CLONE(id) + ) + return response.data + }, } export default apiClient diff --git a/qwen3-tts-frontend/src/lib/constants.ts b/qwen3-tts-frontend/src/lib/constants.ts index 2679419..e67ae10 100644 --- a/qwen3-tts-frontend/src/lib/constants.ts +++ b/qwen3-tts-frontend/src/lib/constants.ts @@ -33,6 +33,7 @@ export const API_ENDPOINTS = { GET: (id: number) => `/voice-designs/${id}`, UPDATE: (id: number) => `/voice-designs/${id}`, DELETE: (id: number) => `/voice-designs/${id}`, + PREPARE_CLONE: (id: number) => `/voice-designs/${id}/prepare-clone`, }, } as const diff --git a/qwen3-tts-frontend/src/types/tts.ts b/qwen3-tts-frontend/src/types/tts.ts index b62f396..0771bae 100644 --- a/qwen3-tts-frontend/src/types/tts.ts +++ b/qwen3-tts-frontend/src/types/tts.ts @@ -47,6 +47,7 @@ export interface VoiceCloneForm { top_p?: number repetition_penalty?: number backend?: string + voice_design_id?: number } export type SpeakerSource = 'builtin' | 'saved-design'