Fix TTS axios/Vertex, generic toast, add stop button to encounter
- TTS: switch from OpenAI SDK to axios (same fix as STT), drop voice param since it's configured inside LiteLLM per model - Fix 'ElevenLabs unavailable' toast shown even when provider is LiteLLM - Add dedicated red Stop button to encounter recording UI
This commit is contained in:
parent
6a8e7e5f33
commit
8b94a49272
4 changed files with 30 additions and 13 deletions
|
|
@ -50,6 +50,7 @@
|
|||
<div class="record-controls">
|
||||
<button id="enc-record-btn" class="record-btn"><i class="fas fa-microphone"></i><span>Start Recording</span></button>
|
||||
<button id="enc-pause-btn" class="btn-sm btn-ghost hidden" style="margin-left:8px;"><i class="fas fa-pause"></i> Pause</button>
|
||||
<button id="enc-stop-btn" class="btn-sm hidden" style="margin-left:8px;background:var(--red);color:white;border:none;border-radius:8px;padding:7px 16px;cursor:pointer;font-size:13px;"><i class="fas fa-stop"></i> Stop</button>
|
||||
<div id="enc-recording-indicator" class="recording-indicator hidden"><div class="pulse-dot"></div><span>Recording... <span id="enc-timer">00:00</span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ function speakText(elementId) {
|
|||
currentlyReadingId = elementId;
|
||||
if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
|
||||
window.speechSynthesis.speak(utter);
|
||||
showToast('ElevenLabs unavailable — using browser voice', 'info');
|
||||
showToast('TTS unavailable — using browser voice', 'info');
|
||||
} else {
|
||||
showToast('Read aloud failed: ' + err.message, 'error');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
var hpiText = document.getElementById('enc-hpi-text');
|
||||
var modelTag = document.getElementById('enc-model-tag');
|
||||
|
||||
var stopBtn = document.getElementById('enc-stop-btn');
|
||||
var recorder = new AudioRecorder();
|
||||
recorder._module = 'encounter';
|
||||
var timer = createTimer(timerEl);
|
||||
|
|
@ -61,6 +62,7 @@
|
|||
recordBtn.classList.add('recording');
|
||||
recordBtn.querySelector('span').textContent = 'Stop';
|
||||
if (pauseBtn) pauseBtn.classList.remove('hidden');
|
||||
if (stopBtn) stopBtn.classList.remove('hidden');
|
||||
indicator.classList.remove('hidden');
|
||||
timer.start();
|
||||
if (recognition) try { recognition.start(); } catch(e) {}
|
||||
|
|
@ -74,6 +76,7 @@
|
|||
recordBtn.classList.remove('recording');
|
||||
recordBtn.querySelector('span').textContent = 'Start Recording';
|
||||
if (pauseBtn) { pauseBtn.classList.add('hidden'); pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause'; }
|
||||
if (stopBtn) stopBtn.classList.add('hidden');
|
||||
indicator.classList.add('hidden');
|
||||
if (recognition) try { recognition.stop(); } catch(e) {}
|
||||
document.dispatchEvent(new CustomEvent('recording-stopped', { detail: { module: 'enc' } }));
|
||||
|
|
@ -105,6 +108,13 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Dedicated stop button (always visible during recording)
|
||||
if (stopBtn) {
|
||||
stopBtn.addEventListener('click', function() {
|
||||
if (isRecording) recordBtn.click();
|
||||
});
|
||||
}
|
||||
|
||||
// Pause / Resume
|
||||
if (pauseBtn) {
|
||||
pauseBtn.addEventListener('click', function() {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const { litellmClient } = require('../utils/ai');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// TTS_PROVIDER=litellm → LiteLLM proxy (routes to any OpenAI-compatible TTS)
|
||||
// TTS_PROVIDER=litellm → LiteLLM proxy (Vertex AI TTS, OpenAI TTS, etc.)
|
||||
// TTS_PROVIDER=elevenlabs → ElevenLabs (not HIPAA)
|
||||
// (auto) LITELLM_API_BASE set → litellm
|
||||
// (auto) ELEVENLABS_API_KEY set → elevenlabs
|
||||
function getTTSProvider() {
|
||||
var env = process.env.TTS_PROVIDER;
|
||||
if (env === 'litellm') return 'litellm';
|
||||
if (env === 'litellm') return 'litellm';
|
||||
if (env === 'elevenlabs') return 'elevenlabs';
|
||||
if (process.env.LITELLM_API_BASE && litellmClient) return 'litellm';
|
||||
if (process.env.LITELLM_API_BASE) return 'litellm';
|
||||
if (process.env.ELEVENLABS_API_KEY) return 'elevenlabs';
|
||||
return 'none';
|
||||
}
|
||||
|
|
@ -26,18 +25,24 @@ router.post('/text-to-speech', authMiddleware, async (req, res) => {
|
|||
if (!text) return res.status(400).json({ error: 'No text provided' });
|
||||
|
||||
if (ttsProvider === 'litellm') {
|
||||
if (!litellmClient) return res.status(400).json({ error: 'LiteLLM not configured. Set LITELLM_API_BASE.' });
|
||||
if (!process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' });
|
||||
var ttsModel = process.env.LITELLM_TTS_MODEL || 'tts-1';
|
||||
var ttsVoice = process.env.LITELLM_TTS_VOICE || 'alloy';
|
||||
var mp3 = await litellmClient.audio.speech.create({
|
||||
var base = process.env.LITELLM_API_BASE.replace(/\/+$/, '');
|
||||
var ttsHeaders = { 'Content-Type': 'application/json' };
|
||||
if (process.env.LITELLM_API_KEY) ttsHeaders['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
|
||||
// Send only model + input — voice is configured inside LiteLLM per model
|
||||
var ttsResp = await axios.post(base + '/v1/audio/speech', {
|
||||
model: ttsModel,
|
||||
voice: ttsVoice,
|
||||
input: text,
|
||||
response_format: 'mp3'
|
||||
input: text
|
||||
}, {
|
||||
headers: ttsHeaders,
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 60000
|
||||
});
|
||||
var buffer = Buffer.from(await mp3.arrayBuffer());
|
||||
|
||||
res.set('Content-Type', 'audio/mpeg');
|
||||
return res.send(buffer);
|
||||
return res.send(Buffer.from(ttsResp.data));
|
||||
}
|
||||
|
||||
if (ttsProvider === 'elevenlabs') {
|
||||
|
|
@ -55,6 +60,7 @@ router.post('/text-to-speech', authMiddleware, async (req, res) => {
|
|||
|
||||
res.status(400).json({ error: 'TTS not configured. Set LITELLM_API_BASE or ELEVENLABS_API_KEY.' });
|
||||
} catch (err) {
|
||||
console.error('[TTS] Error:', err.message);
|
||||
res.status(500).json({ error: 'TTS failed: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue