feat: add Chatterbox TTS engine for multilingual voice cloning

- New ChatterboxTTSBackend wrapping ChatterboxMultilingualTTS (ResembleAI/chatterbox)
- Supports 23 languages including Hebrew, forces CPU on macOS (MPS issue)
- Monkey-patches torch.load for CPU loading, forces eager attention for compatibility
- trim_tts_output utility cuts trailing silence/hallucination from Chatterbox output
- Full engine integration: /generate, /generate/stream, model status/download/delete
- Hebrew (he) added to supported languages in frontend and backend validation
- Single flat model dropdown extended with Chatterbox option in both generation UIs
- ModelManagement UI groups LuxTTS and Chatterbox under 'Other Voice Models' section
This commit is contained in:
James Pine
2026-03-13 02:09:32 -07:00
parent 3576521d62
commit 76bb207b2b
16 changed files with 1401 additions and 346 deletions
@@ -316,7 +316,7 @@ export function FloatingGenerateBox({
</span>
</div>
<AnimatePresence>
{isExpanded && form.watch('engine') !== 'luxtts' && (
{isExpanded && form.watch('engine') === 'qwen' && (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
@@ -407,11 +407,15 @@ export function FloatingGenerateBox({
value={
form.watch('engine') === 'luxtts'
? 'luxtts'
: `qwen:${form.watch('modelSize') || '1.7B'}`
: form.watch('engine') === 'chatterbox'
? 'chatterbox'
: `qwen:${form.watch('modelSize') || '1.7B'}`
}
onValueChange={(value) => {
if (value === 'luxtts') {
form.setValue('engine', 'luxtts');
} else if (value === 'chatterbox') {
form.setValue('engine', 'chatterbox');
} else {
const [, modelSize] = value.split(':');
form.setValue('engine', 'qwen');
@@ -434,6 +438,9 @@ export function FloatingGenerateBox({
<SelectItem value="luxtts" className="text-xs text-muted-foreground">
LuxTTS
</SelectItem>
<SelectItem value="chatterbox" className="text-xs text-muted-foreground">
Chatterbox
</SelectItem>
</SelectContent>
</Select>
</FormItem>
@@ -76,7 +76,7 @@ export function GenerationForm() {
)}
/>
{form.watch('engine') !== 'luxtts' && (
{form.watch('engine') === 'qwen' && (
<FormField
control={form.control}
name="instruct"
@@ -107,11 +107,15 @@ export function GenerationForm() {
value={
form.watch('engine') === 'luxtts'
? 'luxtts'
: `qwen:${form.watch('modelSize') || '1.7B'}`
: form.watch('engine') === 'chatterbox'
? 'chatterbox'
: `qwen:${form.watch('modelSize') || '1.7B'}`
}
onValueChange={(value) => {
if (value === 'luxtts') {
form.setValue('engine', 'luxtts');
} else if (value === 'chatterbox') {
form.setValue('engine', 'chatterbox');
} else {
const [, modelSize] = value.split(':');
form.setValue('engine', 'qwen');
@@ -128,12 +132,15 @@ export function GenerationForm() {
<SelectItem value="qwen:1.7B">Qwen3-TTS 1.7B</SelectItem>
<SelectItem value="qwen:0.6B">Qwen3-TTS 0.6B</SelectItem>
<SelectItem value="luxtts">LuxTTS</SelectItem>
<SelectItem value="chatterbox">Chatterbox</SelectItem>
</SelectContent>
</Select>
<FormDescription>
{form.watch('engine') === 'luxtts'
? 'Fast, English-focused'
: 'Multi-language, two sizes'}
: form.watch('engine') === 'chatterbox'
? 'Multilingual, incl. Hebrew'
: 'Multi-language, two sizes'}
</FormDescription>
</FormItem>