From 1db41b62785a850e5dbf0dc209cbd46a7f1d4ab4 Mon Sep 17 00:00:00 2001 From: bdim404 Date: Tue, 10 Mar 2026 18:05:31 +0800 Subject: [PATCH] feat(audiobook): enhance chapter expansion functionality in ProjectCard component --- qwen3-tts-backend/core/tts_service.py | 86 ++++++++++++++-------- qwen3-tts-frontend/src/pages/Audiobook.tsx | 78 ++++++++------------ 2 files changed, 85 insertions(+), 79 deletions(-) diff --git a/qwen3-tts-backend/core/tts_service.py b/qwen3-tts-backend/core/tts_service.py index 813adcf..c9ed68d 100644 --- a/qwen3-tts-backend/core/tts_service.py +++ b/qwen3-tts-backend/core/tts_service.py @@ -1,3 +1,5 @@ +import asyncio +import functools import time import logging from abc import ABC, abstractmethod @@ -39,16 +41,21 @@ class LocalTTSBackend(TTSBackend): await self.model_manager.load_model("custom-voice") _, tts = await self.model_manager.get_current_model() - result = tts.generate_custom_voice( - text=params['text'], - language=params['language'], - speaker=params['speaker'], - instruct=params.get('instruct', ''), - max_new_tokens=params['max_new_tokens'], - temperature=params['temperature'], - top_k=params['top_k'], - top_p=params['top_p'], - repetition_penalty=params['repetition_penalty'] + loop = asyncio.get_event_loop() + result = await loop.run_in_executor( + None, + functools.partial( + tts.generate_custom_voice, + text=params['text'], + language=params['language'], + speaker=params['speaker'], + instruct=params.get('instruct', ''), + max_new_tokens=params['max_new_tokens'], + temperature=params['temperature'], + top_k=params['top_k'], + top_p=params['top_p'], + repetition_penalty=params['repetition_penalty'], + ) ) import numpy as np @@ -60,15 +67,20 @@ class LocalTTSBackend(TTSBackend): await self.model_manager.load_model("voice-design") _, tts = await self.model_manager.get_current_model() - result = tts.generate_voice_design( - text=params['text'], - language=params['language'], - instruct=params['instruct'], - max_new_tokens=params['max_new_tokens'], - temperature=params['temperature'], - top_k=params['top_k'], - top_p=params['top_p'], - repetition_penalty=params['repetition_penalty'] + loop = asyncio.get_event_loop() + result = await loop.run_in_executor( + None, + functools.partial( + tts.generate_voice_design, + text=params['text'], + language=params['language'], + instruct=params['instruct'], + max_new_tokens=params['max_new_tokens'], + temperature=params['temperature'], + top_k=params['top_k'], + top_p=params['top_p'], + repetition_penalty=params['repetition_penalty'], + ) ) import numpy as np @@ -82,27 +94,37 @@ class LocalTTSBackend(TTSBackend): await self.model_manager.load_model("base") _, tts = await self.model_manager.get_current_model() + loop = asyncio.get_event_loop() + if x_vector is None: if ref_audio_bytes is None: raise ValueError("Either ref_audio_bytes or x_vector must be provided") ref_audio_array, ref_sr = process_ref_audio(ref_audio_bytes) - x_vector = tts.create_voice_clone_prompt( - ref_audio=(ref_audio_array, ref_sr), - ref_text=params.get('ref_text', ''), - x_vector_only_mode=False + x_vector = await loop.run_in_executor( + None, + functools.partial( + tts.create_voice_clone_prompt, + ref_audio=(ref_audio_array, ref_sr), + ref_text=params.get('ref_text', ''), + x_vector_only_mode=False, + ) ) - wavs, sample_rate = tts.generate_voice_clone( - text=params['text'], - language=params['language'], - voice_clone_prompt=x_vector, - max_new_tokens=params['max_new_tokens'], - temperature=params['temperature'], - top_k=params['top_k'], - top_p=params['top_p'], - repetition_penalty=params['repetition_penalty'] + wavs, sample_rate = await loop.run_in_executor( + None, + functools.partial( + tts.generate_voice_clone, + text=params['text'], + language=params['language'], + voice_clone_prompt=x_vector, + max_new_tokens=params['max_new_tokens'], + temperature=params['temperature'], + top_k=params['top_k'], + top_p=params['top_p'], + repetition_penalty=params['repetition_penalty'], + ) ) import numpy as np diff --git a/qwen3-tts-frontend/src/pages/Audiobook.tsx b/qwen3-tts-frontend/src/pages/Audiobook.tsx index 910b999..53babaf 100644 --- a/qwen3-tts-frontend/src/pages/Audiobook.tsx +++ b/qwen3-tts-frontend/src/pages/Audiobook.tsx @@ -323,6 +323,7 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr const [turbo, setTurbo] = useState(false) const [charsCollapsed, setCharsCollapsed] = useState(false) const [chaptersCollapsed, setChaptersCollapsed] = useState(false) + const [expandedChapters, setExpandedChapters] = useState>(new Set()) const prevStatusRef = useRef(project.status) const autoExpandedRef = useRef(new Set()) @@ -691,10 +692,17 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr const chGenerating = chSegs.some(s => s.status === 'generating') const chAllDone = chTotal > 0 && chDone === chTotal const chTitle = ch.title || `第 ${ch.chapter_index + 1} 章` + const chExpanded = expandedChapters.has(ch.id) + const toggleChExpand = () => setExpandedChapters(prev => { + const next = new Set(prev) + if (next.has(ch.id)) next.delete(ch.id) + else next.add(ch.id) + return next + }) return (
- {chTitle} + {chTitle}
{ch.status === 'pending' && ( )} + {chSegs.length > 0 && ( + + )}
{ch.status === 'parsing' && ( )} + {chExpanded && chSegs.length > 0 && ( +
+ {chSegs.map(seg => ( +
+
+ {seg.character_name || '?'} + {seg.text} + {seg.status === 'generating' && } + {seg.status === 'error' && 出错} +
+ {seg.status === 'done' && ( + + )} +
+ ))} +
+ )}
) })} @@ -748,52 +778,6 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr )} - {['generating', 'done'].includes(status) && segments.length > 0 && ( -
-
-
- 片段列表({segments.length} 条) -
- -
-
- {segments.slice(0, 50).map(seg => ( -
-
- {seg.character_name || '?'} - {seg.text} - {seg.status === 'generating' ? ( -
- -
- ) : seg.status !== 'done' ? ( - - {seg.status === 'error' ? '出错' : '待生成'} - - ) : null} -
- {seg.status === 'done' && ( - - )} -
- ))} - {segments.length > 50 && ( -
- 仅显示前 50 条,共 {segments.length} 条 -
- )} -
-
- )} )}