2 Commits
6 changed files with 47 additions and 14 deletions
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+29 -1
View File
@@ -12,7 +12,7 @@ import aiohttp
from fastapi import FastAPI, UploadFile, File, HTTPException, Form from fastapi import FastAPI, UploadFile, File, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse from fastapi.responses import FileResponse, Response
from pydantic import BaseModel from pydantic import BaseModel
# 导入 TTS 服务 # 导入 TTS 服务
@@ -255,6 +255,34 @@ async def synthesize_tts(text: str = Form(...), provider: Optional[str] = Form(N
app.mount("/audio", StaticFiles(directory=AUDIO_DIR), name="audio") app.mount("/audio", StaticFiles(directory=AUDIO_DIR), name="audio")
# ChatTTS 音频代理(解决 HTTPS 页面访问 HTTP 资源问题)
@app.get("/chattts/audio/{filename}")
async def proxy_chattts_audio(filename: str):
"""代理 ChatTTS 音频文件"""
import aiohttp
chattts_url = os.getenv("CHATTTS_URL", "http://192.168.2.5:12002")
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{chattts_url}/audio/{filename}",
timeout=aiohttp.ClientTimeout(total=30)
) as resp:
if resp.status != 200:
raise HTTPException(status_code=404, detail="Audio not found")
audio_data = await resp.read()
return Response(
content=audio_data,
media_type="audio/wav",
headers={"Cache-Control": "public, max-age=3600"}
)
except Exception as e:
logger.error(f"Proxy audio error: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=PORT) uvicorn.run(app, host="0.0.0.0", port=PORT)
+12 -11
View File
@@ -879,6 +879,8 @@
const msg = document.createElement('div'); const msg = document.createElement('div');
msg.className = `message ${role}`; msg.className = `message ${role}`;
let audioBtnId = ''; // 在函数顶部声明
if (role === 'user' && content instanceof Blob) { if (role === 'user' && content instanceof Blob) {
const audioUrl = URL.createObjectURL(content); const audioUrl = URL.createObjectURL(content);
msg.innerHTML = ` msg.innerHTML = `
@@ -892,7 +894,6 @@
`; `;
} else if (role === 'assistant') { } else if (role === 'assistant') {
let audioHtml = ''; let audioHtml = '';
let audioBtnId = '';
if (audioData) { if (audioData) {
audioBtnId = `audioBtn_${Date.now()}`; audioBtnId = `audioBtn_${Date.now()}`;
audioHtml = `<button class="play-btn tts-play-btn" id="${audioBtnId}" onclick="playAudio('${audioData}', this)"> audioHtml = `<button class="play-btn tts-play-btn" id="${audioBtnId}" onclick="playAudio('${audioData}', this)">
@@ -904,22 +905,22 @@
<div class="role">AI</div> <div class="role">AI</div>
<div class="content">${content}${audioHtml}</div> <div class="content">${content}${audioHtml}</div>
`; `;
// 自动播放(如果开启)
if (audioData && autoPlay) {
setTimeout(() => {
const btn = document.getElementById(audioBtnId);
if (btn) {
playAudio(audioData, btn);
}
}, 100);
}
} else { } else {
msg.innerHTML = `<div class="role">我</div><div class="content">${content}</div>`; msg.innerHTML = `<div class="role">我</div><div class="content">${content}</div>`;
} }
chatSection.appendChild(msg); chatSection.appendChild(msg);
chatSection.scrollTop = chatSection.scrollHeight; chatSection.scrollTop = chatSection.scrollHeight;
// 自动播放(在元素添加到DOM后)
if (role === 'assistant' && audioData && autoPlay && audioBtnId) {
setTimeout(() => {
const btn = document.getElementById(audioBtnId);
if (btn) {
playAudio(audioData, btn);
}
}, 100);
}
} }
// 播放音频 // 播放音频
+6 -2
View File
@@ -134,8 +134,12 @@ class ChatTTSProvider(TTSProvider):
raise Exception(f"ChatTTS error: {error}") raise Exception(f"ChatTTS error: {error}")
data = await resp.json() data = await resp.json()
# ChatTTS 返回的 URL 是相对路径,需要拼接 # ChatTTS 返回的 URL 是 /audio/xxx.wav
audio_url = f"{self.CHATTTS_URL}{data['audio_url']}" # 改用本地代理路径(解决 HTTPS 页面访问 HTTP 问题)
original_url = data['audio_url']
# /audio/xxx.wav -> /chattts/audio/xxx.wav (通过本地代理)
filename = original_url.split('/')[-1]
audio_url = f"/chattts/audio/{filename}"
return None, audio_url return None, audio_url
def get_name(self) -> str: def get_name(self) -> str: