Spaces:
Sleeping
Sleeping
Fix TTS CORS issues with backend proxy endpoint
Browse filesAdd TTS synthesis proxy at /tts/synthesize to avoid CORS:
- Backend calls Gradio TTS service directly (no CORS issues)
- Frontend calls our proxy endpoint instead of external service
- Add gradio-client dependency for TTS integration
- Simplify TTS workflow with direct response handling
Resolves TTS CORS and 502 Bad Gateway errors
v1.0.10
π€ Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- app/api/chat_widget.py +9 -23
- app/api/main.py +34 -1
- requirements.txt +2 -1
app/api/chat_widget.py
CHANGED
|
@@ -414,45 +414,31 @@ async def chat_widget(request: Request):
|
|
| 414 |
try {{
|
| 415 |
console.log('π΅ Synthesizing TTS for:', text.substring(0, 50) + '...');
|
| 416 |
|
| 417 |
-
// Call TTS
|
| 418 |
-
const response = await fetch('
|
| 419 |
method: 'POST',
|
| 420 |
headers: {{
|
| 421 |
'Content-Type': 'application/json',
|
| 422 |
}},
|
| 423 |
body: JSON.stringify({{
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
'expresso/ex03-ex01_happy_001_channel1_334s.wav'
|
| 427 |
-
]
|
| 428 |
}})
|
| 429 |
}});
|
| 430 |
|
| 431 |
if (!response.ok) {{
|
| 432 |
-
throw new Error(`TTS
|
| 433 |
}}
|
| 434 |
|
| 435 |
const result = await response.json();
|
| 436 |
console.log('π΅ TTS response:', result);
|
| 437 |
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
if (result.event_id) {{
|
| 441 |
-
// If queued, wait for completion
|
| 442 |
-
audioUrl = await this.waitForTTSResult(result.event_id);
|
| 443 |
-
}} else if (result.data && result.data[0]) {{
|
| 444 |
-
// Direct response
|
| 445 |
-
audioUrl = result.data[0];
|
| 446 |
-
}} else {{
|
| 447 |
-
throw new Error('No audio URL in TTS response');
|
| 448 |
-
}}
|
| 449 |
-
|
| 450 |
-
if (!audioUrl) {{
|
| 451 |
-
throw new Error('TTS generation failed - no audio URL');
|
| 452 |
}}
|
| 453 |
|
| 454 |
-
// Convert relative URL to absolute
|
| 455 |
-
const fullAudioUrl =
|
| 456 |
console.log('π Fetching audio from:', fullAudioUrl);
|
| 457 |
|
| 458 |
// Fetch the audio file
|
|
|
|
| 414 |
try {{
|
| 415 |
console.log('π΅ Synthesizing TTS for:', text.substring(0, 50) + '...');
|
| 416 |
|
| 417 |
+
// Call our TTS proxy endpoint (no CORS issues)
|
| 418 |
+
const response = await fetch('{base_url}/tts/synthesize', {{
|
| 419 |
method: 'POST',
|
| 420 |
headers: {{
|
| 421 |
'Content-Type': 'application/json',
|
| 422 |
}},
|
| 423 |
body: JSON.stringify({{
|
| 424 |
+
text: text,
|
| 425 |
+
voice: 'expresso/ex03-ex01_happy_001_channel1_334s.wav'
|
|
|
|
|
|
|
| 426 |
}})
|
| 427 |
}});
|
| 428 |
|
| 429 |
if (!response.ok) {{
|
| 430 |
+
throw new Error(`TTS proxy error: ${{response.status}}`);
|
| 431 |
}}
|
| 432 |
|
| 433 |
const result = await response.json();
|
| 434 |
console.log('π΅ TTS response:', result);
|
| 435 |
|
| 436 |
+
if (!result.success || !result.audio_url) {{
|
| 437 |
+
throw new Error('TTS generation failed');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
}}
|
| 439 |
|
| 440 |
+
// Convert relative URL to absolute if needed
|
| 441 |
+
const fullAudioUrl = result.audio_url.startsWith('http') ? result.audio_url : `https://pgits-kyutai-tts-service-v3.hf.space/file=${{result.audio_url}}`;
|
| 442 |
console.log('π Fetching audio from:', fullAudioUrl);
|
| 443 |
|
| 444 |
// Fetch the audio file
|
app/api/main.py
CHANGED
|
@@ -408,11 +408,44 @@ async def stream_chat(request: ChatRequest):
|
|
| 408 |
"Access-Control-Allow-Origin": "*",
|
| 409 |
}
|
| 410 |
)
|
| 411 |
-
|
| 412 |
except Exception as e:
|
| 413 |
raise HTTPException(status_code=500, detail=f"Stream chat error: {str(e)}")
|
| 414 |
|
| 415 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
@app.get("/auth/login", response_model=AuthResponse)
|
| 417 |
async def google_auth_login(request: Request, state: Optional[str] = None):
|
| 418 |
"""Initiate Google OAuth login."""
|
|
|
|
| 408 |
"Access-Control-Allow-Origin": "*",
|
| 409 |
}
|
| 410 |
)
|
| 411 |
+
|
| 412 |
except Exception as e:
|
| 413 |
raise HTTPException(status_code=500, detail=f"Stream chat error: {str(e)}")
|
| 414 |
|
| 415 |
|
| 416 |
+
@app.post("/tts/synthesize")
|
| 417 |
+
async def tts_synthesize(request: Dict[str, Any]):
|
| 418 |
+
"""TTS synthesis proxy to avoid CORS issues."""
|
| 419 |
+
try:
|
| 420 |
+
from gradio_client import Client
|
| 421 |
+
|
| 422 |
+
text = request.get("text", "")
|
| 423 |
+
voice = request.get("voice", "expresso/ex03-ex01_happy_001_channel1_334s.wav")
|
| 424 |
+
|
| 425 |
+
if not text.strip():
|
| 426 |
+
raise HTTPException(status_code=400, detail="Text is required")
|
| 427 |
+
|
| 428 |
+
logger.info(f"π΅ TTS synthesis request: {text[:50]}...")
|
| 429 |
+
|
| 430 |
+
# Connect to TTS service
|
| 431 |
+
client = Client("https://pgits-kyutai-tts-service-v3.hf.space")
|
| 432 |
+
result = client.predict(text, voice, api_name="/predict")
|
| 433 |
+
|
| 434 |
+
if not result:
|
| 435 |
+
raise HTTPException(status_code=500, detail="TTS generation failed")
|
| 436 |
+
|
| 437 |
+
# Return the audio file URL
|
| 438 |
+
return {
|
| 439 |
+
"success": True,
|
| 440 |
+
"audio_url": result,
|
| 441 |
+
"text": text
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
except Exception as e:
|
| 445 |
+
logger.error(f"TTS synthesis error: {e}")
|
| 446 |
+
raise HTTPException(status_code=500, detail=f"TTS synthesis failed: {str(e)}")
|
| 447 |
+
|
| 448 |
+
|
| 449 |
@app.get("/auth/login", response_model=AuthResponse)
|
| 450 |
async def google_auth_login(request: Request, state: Optional[str] = None):
|
| 451 |
"""Initiate Google OAuth login."""
|
requirements.txt
CHANGED
|
@@ -19,4 +19,5 @@ python-multipart>=0.0.6
|
|
| 19 |
python-jose>=3.3.0
|
| 20 |
PyJWT>=2.8.0
|
| 21 |
httpx>=0.25.0
|
| 22 |
-
python-dotenv>=1.0.0
|
|
|
|
|
|
| 19 |
python-jose>=3.3.0
|
| 20 |
PyJWT>=2.8.0
|
| 21 |
httpx>=0.25.0
|
| 22 |
+
python-dotenv>=1.0.0
|
| 23 |
+
gradio-client>=0.17.0
|