feat(core): add ModelScope support for TTS and new office load status

Add support to download and load TTS model from ModelScope, with a fallback to the HuggingFace mirror.
Implement a `documentLoadStatus` property and helper functions in `office.js` to track file loading state.
Improve request cancellation logic in `api.js`, ensuring proper cancel URL resolution and request‑id handling.

These changes enhance robustness, reduce external dependencies, and provide better UX for office file handling.
This commit is contained in:
2026-04-11 10:04:34 +08:00
parent d8b7832b14
commit f99acf5d50
5 changed files with 248 additions and 186 deletions
+50 -14
View File
@@ -25,6 +25,10 @@ router = APIRouter()
# Global TTS model instance
_tts_model: Optional["Qwen3TTSModel"] = None
# Model paths for loading
MODEL_ID_HF = "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign"
MODEL_ID_MS = "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign"
def _get_device_map() -> str:
"""设备检测逻辑:优先 CUDA,其次 MPS,最后 CPU"""
@@ -38,6 +42,24 @@ def _get_device_map() -> str:
return "cpu"
def _download_model_from_modelscope() -> Optional[str]:
"""从 ModelScope 下载模型到本地临时目录"""
try:
from modelscope import snapshot_download
cache_dir = os.path.join(os.path.dirname(__file__), "models")
os.makedirs(cache_dir, exist_ok=True)
model_dir = snapshot_download(
MODEL_ID_MS,
cache_dir=cache_dir,
revision="master"
)
logger.info("ModelScope 模型下载完成: %s", model_dir)
return model_dir
except Exception as e:
logger.warning("ModelScope 下载失败: %s", e)
return None
async def _warmup_tts():
"""预热 TTS 模型"""
await asyncio.to_thread(_load_tts_model_with_retry)
@@ -58,28 +80,42 @@ def _load_tts_model_with_retry(max_retries: int = 3) -> "Qwen3TTSModel":
if Qwen3TTSModel is None:
raise RuntimeError("qwen_tts 库未安装,无法加载 TTS 模型")
candidates = [
"Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign",
"ModelScope/Qwen3-TTS-12Hz-1.7B-VoiceDesign",
]
device_map = _get_device_map()
last_err = None
for i, model_id in enumerate(candidates, start=1):
# 策略1: 尝试从 ModelScope 下载后加载
for attempt in range(max_retries):
try:
logger.info("尝试从 ModelScope 下载模型...")
model_path = _download_model_from_modelscope()
if model_path and os.path.isdir(model_path):
_tts_model = Qwen3TTSModel.from_pretrained(
model_path,
device_map=device_map,
dtype=torch.float16,
)
logger.info("ModelScope 模型加载成功: %s", model_path)
return _tts_model
except Exception as e:
logger.warning("ModelScope 加载失败 (尝试 %d/%d): %s", attempt + 1, max_retries, e)
last_err = e
# 策略2: 尝试从 HuggingFace 镜像加载
for attempt in range(max_retries):
try:
logger.info("尝试从 HuggingFace 镜像加载模型...")
_tts_model = Qwen3TTSModel.from_pretrained(
model_id,
MODEL_ID_HF,
device_map=device_map,
dtype=torch.float16,
attn_implementation="flash_attention_2",
)
logger.info("Loaded TTS model from %s", model_id)
logger.info("HuggingFace 模型加载成功")
return _tts_model
except Exception as e:
logger.warning("Failed to load TTS model from %s: %s", model_id, e)
logger.warning("HuggingFace 加载失败 (尝试 %d/%d): %s", attempt + 1, max_retries, e)
last_err = e
if i >= max_retries:
break
raise RuntimeError(f"Unable to load TTS model from sources: {candidates}") from last_err
raise RuntimeError(f"无法加载 TTS 模型: {last_err}") from last_err
class TTSRequest(BaseModel):
@@ -160,10 +196,10 @@ async def tts_endpoint(req: TTSRequest):
speaker = req.speaker or "Vivian"
try:
wavs_sr = model.generate_custom_voice(
# VoiceDesign 模型使用 generate_voice_design 方法
wavs_sr = model.generate_voice_design(
text=text,
language="Chinese",
speaker=speaker,
instruct=instruct,
)
except Exception as e: