feat: Add voice cloning support and prepare clone functionality in voice design
This commit is contained in:
@@ -160,6 +160,20 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
|
|||||||
|
|
||||||
let result
|
let result
|
||||||
if (selectedItem?.source === 'saved-design') {
|
if (selectedItem?.source === 'saved-design') {
|
||||||
|
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({
|
result = await ttsApi.createVoiceDesignJob({
|
||||||
text: data.text,
|
text: data.text,
|
||||||
language: data.language,
|
language: data.language,
|
||||||
@@ -171,6 +185,7 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
|
|||||||
top_p: data.top_p,
|
top_p: data.top_p,
|
||||||
repetition_penalty: data.repetition_penalty,
|
repetition_penalty: data.repetition_penalty,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result = await ttsApi.createCustomVoiceJob(data)
|
result = await ttsApi.createCustomVoiceJob(data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
|
|||||||
})
|
})
|
||||||
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
||||||
const [saveDesignName, setSaveDesignName] = useState('')
|
const [saveDesignName, setSaveDesignName] = useState('')
|
||||||
|
const [isPreparing, setIsPreparing] = useState(false)
|
||||||
|
|
||||||
const { currentJob, isPolling, isCompleted, startPolling, elapsedTime } = useJobPolling()
|
const { currentJob, isPolling, isCompleted, startPolling, elapsedTime } = useJobPolling()
|
||||||
const { refresh } = useHistoryContext()
|
const { refresh } = useHistoryContext()
|
||||||
@@ -128,13 +129,29 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
|
|||||||
toast.error('请输入设计名称')
|
toast.error('请输入设计名称')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await voiceDesignApi.create({
|
const backend = preferences?.default_backend || 'local'
|
||||||
|
const design = await voiceDesignApi.create({
|
||||||
name: saveDesignName,
|
name: saveDesignName,
|
||||||
instruct: instruct,
|
instruct: instruct,
|
||||||
backend_type: preferences?.default_backend || 'local'
|
backend_type: backend
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success('音色设计已保存')
|
toast.success('音色设计已保存')
|
||||||
|
|
||||||
|
if (backend === 'local') {
|
||||||
|
setIsPreparing(true)
|
||||||
|
try {
|
||||||
|
await voiceDesignApi.prepareClone(design.id)
|
||||||
|
toast.success('音色克隆准备完成')
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('准备克隆失败,但设计已保存')
|
||||||
|
} finally {
|
||||||
|
setIsPreparing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setShowSaveDialog(false)
|
setShowSaveDialog(false)
|
||||||
setSaveDesignName('')
|
setSaveDesignName('')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -238,8 +255,8 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
|
|||||||
}}>
|
}}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="button" onClick={handleSaveDesign}>
|
<Button type="button" onClick={handleSaveDesign} disabled={isPreparing}>
|
||||||
保存
|
{isPreparing ? '准备中...' : '保存'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -259,6 +259,9 @@ export const ttsApi = {
|
|||||||
if (data.x_vector_only_mode !== undefined) {
|
if (data.x_vector_only_mode !== undefined) {
|
||||||
formData.append('x_vector_only_mode', String(data.x_vector_only_mode))
|
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) {
|
if (data.max_new_tokens !== undefined) {
|
||||||
formData.append('max_new_tokens', String(data.max_new_tokens))
|
formData.append('max_new_tokens', String(data.max_new_tokens))
|
||||||
}
|
}
|
||||||
@@ -422,6 +425,13 @@ export const voiceDesignApi = {
|
|||||||
delete: async (id: number): Promise<void> => {
|
delete: async (id: number): Promise<void> => {
|
||||||
await apiClient.delete(API_ENDPOINTS.VOICE_DESIGNS.DELETE(id))
|
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
|
export default apiClient
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const API_ENDPOINTS = {
|
|||||||
GET: (id: number) => `/voice-designs/${id}`,
|
GET: (id: number) => `/voice-designs/${id}`,
|
||||||
UPDATE: (id: number) => `/voice-designs/${id}`,
|
UPDATE: (id: number) => `/voice-designs/${id}`,
|
||||||
DELETE: (id: number) => `/voice-designs/${id}`,
|
DELETE: (id: number) => `/voice-designs/${id}`,
|
||||||
|
PREPARE_CLONE: (id: number) => `/voice-designs/${id}/prepare-clone`,
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface VoiceCloneForm {
|
|||||||
top_p?: number
|
top_p?: number
|
||||||
repetition_penalty?: number
|
repetition_penalty?: number
|
||||||
backend?: string
|
backend?: string
|
||||||
|
voice_design_id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SpeakerSource = 'builtin' | 'saved-design'
|
export type SpeakerSource = 'builtin' | 'saved-design'
|
||||||
|
|||||||
Reference in New Issue
Block a user