3 Commits
8 changed files with 56 additions and 21 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -3,3 +3,4 @@ uvicorn==0.27.1
python-multipart==0.0.9 python-multipart==0.0.9
aiohttp==3.9.3 aiohttp==3.9.3
edge-tts==6.1.9 edge-tts==6.1.9
requests==2.31.0
+14 -7
View File
@@ -372,9 +372,9 @@
<div class="name">🌐 Edge TTS</div> <div class="name">🌐 Edge TTS</div>
<div class="status" id="edgeStatus">检测中...</div> <div class="status" id="edgeStatus">检测中...</div>
</div> </div>
<div class="tts-option disabled" data-provider="chattts"> <div class="tts-option" data-provider="chattts">
<div class="name">🤖 ChatTTS</div> <div class="name">🤖 ChatTTS</div>
<div class="status">暂未部署</div> <div class="status" id="chatttsStatus">检测中...</div>
</div> </div>
</div> </div>
<div class="voice-select" id="voiceSelect" style="display: none;"> <div class="voice-select" id="voiceSelect" style="display: none;">
@@ -490,13 +490,20 @@
// 更新状态 // 更新状态
data.providers.forEach(p => { data.providers.forEach(p => {
if (p.name === 'edge') { const statusElId = p.name === 'edge' ? 'edgeStatus' :
const statusEl = document.getElementById('edgeStatus'); p.name === 'chattts' ? 'chatttsStatus' : null;
if (statusElId) {
const statusEl = document.getElementById(statusElId);
statusEl.textContent = p.available ? '可用 ✓' : '不可用'; statusEl.textContent = p.available ? '可用 ✓' : '不可用';
const optionEl = ttsOptions.querySelector('[data-provider="edge"]'); const optionEl = ttsOptions.querySelector(`[data-provider="${p.name}"]`);
if (p.available) { if (optionEl) {
optionEl.classList.remove('disabled'); if (p.available) {
optionEl.classList.remove('disabled');
} else {
optionEl.classList.add('disabled');
}
} }
} }
}); });
+40 -13
View File
@@ -108,32 +108,59 @@ class EdgeTTSProvider(TTSProvider):
class ChatTTSProvider(TTSProvider): class ChatTTSProvider(TTSProvider):
"""ChatTTS 提供者(本地部署,预留接口""" """ChatTTS 提供者(本地部署)"""
# 预留配置 # ChatTTS 服务地址
CHATTTS_URL = os.getenv("CHATTTS_URL", "http://localhost:19020") CHATTTS_URL = os.getenv("CHATTTS_URL", "http://192.168.2.5:12002")
def __init__(self): def __init__(self):
self._available = False # 暂不可用 self._available = None
async def synthesize(self, text: str) -> Tuple[str, str]: async def synthesize(self, text: str) -> Tuple[str, str]:
""" """使用 ChatTTS 合成语音"""
使用 ChatTTS 合成语音 import aiohttp
TODO: 后续实现
""" async with aiohttp.ClientSession() as session:
raise NotImplementedError("ChatTTS 尚未实现,请先部署 ChatTTS 服务") form = aiohttp.FormData()
form.add_field('text', text)
async with session.post(
f"{self.CHATTTS_URL}/synthesize",
data=form,
timeout=aiohttp.ClientTimeout(total=60)
) as resp:
if resp.status != 200:
error = await resp.text()
raise Exception(f"ChatTTS error: {error}")
data = await resp.json()
# ChatTTS 返回的 URL 是相对路径,需要拼接
audio_url = f"{self.CHATTTS_URL}{data['audio_url']}"
return None, audio_url
def get_name(self) -> str: def get_name(self) -> str:
return "ChatTTS" return "ChatTTS"
def is_available(self) -> bool: def is_available(self) -> bool:
"""检查 ChatTTS 是否可用""" """检查 ChatTTS 是否可用"""
# TODO: 后续实现检测逻辑 if self._available is None:
try:
import requests
resp = requests.get(f"{self.CHATTTS_URL}/health", timeout=5)
if resp.status_code == 200:
data = resp.json()
self._available = data.get("status") == "ok"
else:
self._available = False
except Exception as e:
logger.warning(f"ChatTTS check failed: {e}")
self._available = False
return self._available return self._available
def set_available(self, available: bool): def set_url(self, url: str):
"""设置可用状态(部署后调用)""" """设置服务地址"""
self._available = available self.CHATTTS_URL = url
self._available = None # 重新检测
class NoTTSProvider(TTSProvider): class NoTTSProvider(TTSProvider):