rebrand to Pediatric Clinical Tools, add diagrams tab, simplify litellm transcribe/tts
This commit is contained in:
parent
cf1d88f36b
commit
0710c4de8e
7 changed files with 76 additions and 101 deletions
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 – Page Not Found | Pediatric AI Scribe</title>
|
||||
<title>404 – Page Not Found | Pediatric Clinical Tools</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
|
|
@ -162,14 +162,14 @@
|
|||
</div>
|
||||
|
||||
<h1>This page doesn't exist</h1>
|
||||
<p>The URL you visited isn't part of Pediatric AI Scribe.<br>It may have been mistyped or the link is outdated.</p>
|
||||
<p>The URL you visited isn't part of Pediatric Clinical Tools.<br>It may have been mistyped or the link is outdated.</p>
|
||||
|
||||
<a href="/" class="btn">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M10.707 2.293a1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 1.414 1.414L4 10.414V17a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3h2v3a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-6.586l.293.293a1 1 0 0 0 1.414-1.414l-7-7z"/></svg>
|
||||
Back to the app
|
||||
</a>
|
||||
|
||||
<div class="footer-note">Pediatric AI Scribe — Clinical Documentation Platform</div>
|
||||
<div class="footer-note">Pediatric Clinical Tools — Clinical Documentation Platform</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="module-header">
|
||||
<h2><i class="fas fa-circle-question"></i> Frequently Asked Questions</h2>
|
||||
<p>Learn how Pediatric AI Scribe works and get the most out of it</p>
|
||||
<p>Learn how Pediatric Clinical Tools works and get the most out of it</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-container">
|
||||
|
|
@ -10,9 +10,9 @@
|
|||
<h3 class="faq-section-title"><i class="fas fa-rocket"></i> Getting Started</h3>
|
||||
|
||||
<div class="faq-item">
|
||||
<button class="faq-question">What is Pediatric AI Scribe?</button>
|
||||
<button class="faq-question">What is Pediatric Clinical Tools?</button>
|
||||
<div class="faq-answer">
|
||||
<p>Pediatric AI Scribe is an AI-powered clinical documentation tool designed specifically for pediatric medicine. It helps physicians generate structured clinical notes from voice recordings or typed text, saving time on documentation so you can focus on patient care.</p>
|
||||
<p>Pediatric Clinical Tools is an AI-powered clinical documentation tool designed specifically for pediatric medicine. It helps physicians generate structured clinical notes from voice recordings or typed text, saving time on documentation so you can focus on patient care.</p>
|
||||
<p>It supports HPIs, SOAP notes, hospital courses, chart reviews, well visits, sick visits, developmental milestone assessments, and more.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pediatric AI Scribe</title>
|
||||
<title>Pediatric Clinical Tools</title>
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
<div class="auth-box">
|
||||
<div class="auth-logo">
|
||||
<i class="fas fa-stethoscope"></i>
|
||||
<h1>Pediatric AI Scribe</h1>
|
||||
<h1>Pediatric Clinical Tools</h1>
|
||||
<p>AI-Powered Clinical Documentation</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
</div>
|
||||
|
||||
<details style="margin:16px 0 0;text-align:left;max-width:480px;margin-left:auto;margin-right:auto;">
|
||||
<summary style="cursor:pointer;font-size:13px;font-weight:600;color:#64748b;text-align:center;">About Pediatric AI Scribe</summary>
|
||||
<summary style="cursor:pointer;font-size:13px;font-weight:600;color:#64748b;text-align:center;">About Pediatric Clinical Tools</summary>
|
||||
<div style="padding:12px 0;font-size:12px;color:#64748b;line-height:1.7;">
|
||||
<p style="margin:0 0 8px;">AI-powered clinical documentation for pediatric medicine. Generate HPIs, SOAP notes, hospital courses, chart reviews, well/sick visit notes, and developmental milestone assessments from voice or text.</p>
|
||||
<p style="margin:0 0 8px;">Includes pediatric calculators (BP percentile, BMI, growth charts, bilirubin nomograms, vital signs), Learning Hub with quizzes and AI-generated content, and comprehensive security (2FA, SSO, session management, audit logging).</p>
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
<footer class="app-footer">
|
||||
Committed to healthcare equity — always consult a qualified healthcare professional for medical advice.
|
||||
•
|
||||
© 2024-2026 Pediatric AI Scribe by PedsHub. All rights reserved.
|
||||
© 2024-2026 Pediatric Clinical Tools by PedsHub. All rights reserved.
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
<div class="logo">
|
||||
<i class="fas fa-stethoscope"></i>
|
||||
<div>
|
||||
<h1>Pediatric AI Scribe</h1>
|
||||
<h1>Pediatric Clinical Tools</h1>
|
||||
<p class="tagline">Welcome, <span id="user-name">Doctor</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -266,6 +266,10 @@
|
|||
<i class="fas fa-note-sticky"></i>
|
||||
<span>Notes</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="diagrams">
|
||||
<i class="fas fa-diagram-project"></i>
|
||||
<span>Diagrams</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="learning">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
<span>Learning Hub</span>
|
||||
|
|
@ -324,6 +328,7 @@
|
|||
<section id="calculators-tab" class="tab-content" data-component="calculators"></section>
|
||||
<section id="extensions-tab" class="tab-content" data-component="extensions"></section>
|
||||
<section id="notes-tab" class="tab-content" data-component="notes"></section>
|
||||
<section id="diagrams-tab" class="tab-content" data-component="diagrams"></section>
|
||||
<section id="learning-tab" class="tab-content" data-component="learning"></section>
|
||||
<section id="cms-tab" class="tab-content" data-component="cms"></section>
|
||||
<section id="docs-tab" class="tab-content" data-component="admin-docs"></section>
|
||||
|
|
@ -339,7 +344,7 @@
|
|||
</div><!-- /.app-body -->
|
||||
|
||||
<footer style="padding:12px 24px;text-align:center;font-size:12px;color:var(--g400);border-top:1px solid var(--g100);">
|
||||
<a href="#" id="btn-show-about" style="color:var(--g500);text-decoration:none;">About Pediatric AI Scribe</a>
|
||||
<a href="#" id="btn-show-about" style="color:var(--g500);text-decoration:none;">About Pediatric Clinical Tools</a>
|
||||
•
|
||||
© <span id="app-year"></span> PedsHub. All rights reserved.
|
||||
</footer>
|
||||
|
|
@ -348,7 +353,7 @@
|
|||
<div id="about-modal" class="confirm-modal hidden">
|
||||
<div class="confirm-modal-box" style="max-width:600px;max-height:80vh;overflow-y:auto;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
||||
<h2 style="margin:0;font-size:18px;color:var(--g800);"><i class="fas fa-stethoscope" style="color:var(--blue);margin-right:8px;"></i>Pediatric AI Scribe</h2>
|
||||
<h2 style="margin:0;font-size:18px;color:var(--g800);"><i class="fas fa-stethoscope" style="color:var(--blue);margin-right:8px;"></i>Pediatric Clinical Tools</h2>
|
||||
<button id="btn-close-about" class="btn-sm btn-ghost" style="padding:4px 8px;"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<p style="color:var(--g600);line-height:1.7;font-size:13px;margin:0 0 14px;">An AI-powered clinical documentation platform designed specifically for pediatric medicine. Built to help physicians spend less time on documentation and more time with patients.</p>
|
||||
|
|
@ -480,6 +485,7 @@
|
|||
<script defer src="/js/peGuide.js"></script>
|
||||
<script defer src="/js/extensions.js"></script>
|
||||
<script defer src="/js/notes.js"></script>
|
||||
<script defer src="/js/diagrams.js"></script>
|
||||
<script defer src="/js/nextcloud.js"></script>
|
||||
<script defer src="/js/wellVisit.js"></script>
|
||||
<script defer src="/js/shadess.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Pediatric AI Scribe",
|
||||
"short_name": "PedScribe",
|
||||
"name": "Pediatric Clinical Tools",
|
||||
"short_name": "PedTools",
|
||||
"description": "AI-powered pediatric clinical documentation",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ app.use('/api', require('./src/routes/logs'));
|
|||
app.use('/api', require('./src/routes/encounters'));
|
||||
app.use('/api', require('./src/routes/memories'));
|
||||
app.use('/api', require('./src/routes/notes'));
|
||||
app.use('/api', require('./src/routes/diagrams'));
|
||||
app.use('/api', require('./src/routes/documents'));
|
||||
app.use('/api', require('./src/routes/audioBackups'));
|
||||
app.use('/api', require('./src/routes/billing'));
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ var { gatewayUrl } = require('../utils/errors');
|
|||
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } });
|
||||
|
||||
// Provider priority (auto-detect):
|
||||
// TRANSCRIBE_PROVIDER=google → Vertex AI / Gemini (direct, HIPAA eligible)
|
||||
// TRANSCRIBE_PROVIDER=aws → Amazon Transcribe (HIPAA eligible)
|
||||
// TRANSCRIBE_PROVIDER=local → local whisper.cpp / faster-whisper
|
||||
// TRANSCRIBE_PROVIDER=openai → OpenAI Whisper (direct)
|
||||
// TRANSCRIBE_PROVIDER=litellm→ LiteLLM proxy (explicit only — audio routing bugs)
|
||||
// TRANSCRIBE_PROVIDER=google → Vertex AI / Gemini (direct, HIPAA eligible)
|
||||
// TRANSCRIBE_PROVIDER=aws → Amazon Transcribe (HIPAA eligible)
|
||||
// TRANSCRIBE_PROVIDER=local → local whisper.cpp / faster-whisper
|
||||
// TRANSCRIBE_PROVIDER=openai → OpenAI Whisper (direct)
|
||||
// TRANSCRIBE_PROVIDER=litellm → LiteLLM proxy at /v1/audio/transcriptions
|
||||
// Auto: google > aws > openai
|
||||
function getTranscribeProvider() {
|
||||
var env = process.env.TRANSCRIBE_PROVIDER;
|
||||
|
|
@ -91,62 +91,29 @@ router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, r
|
|||
|
||||
if (provider === 'litellm') {
|
||||
if (!process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' });
|
||||
var sttModel = userModel || adminSttModel || process.env.LITELLM_STT_MODEL || 'gemini-2.0-flash';
|
||||
var axios = require('axios');
|
||||
var sttModel = userModel || adminSttModel || process.env.LITELLM_STT_MODEL || 'whisper-1';
|
||||
var mimeType = req.file.mimetype || 'audio/webm';
|
||||
var ext = mimeType.split('/')[1] || 'webm';
|
||||
|
||||
// Models that use /audio/transcriptions (Whisper, Deepgram, Groq Whisper, etc.)
|
||||
var isTranscriptionModel = /whisper|deepgram|nova|groq|scribe|elevenlabs|transcri/i.test(sttModel);
|
||||
var file = new File([req.file.buffer], 'audio.' + ext, { type: mimeType });
|
||||
var form = new FormData();
|
||||
form.append('file', file);
|
||||
form.append('model', sttModel);
|
||||
var fetchHeaders = {};
|
||||
if (process.env.LITELLM_API_KEY) fetchHeaders['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
|
||||
if (isTranscriptionModel) {
|
||||
// Use /v1/audio/transcriptions endpoint (OpenAI-compatible)
|
||||
var ext = mimeType.split('/')[1] || 'webm';
|
||||
var file = new File([req.file.buffer], 'audio.' + ext, { type: mimeType });
|
||||
var form = new FormData();
|
||||
form.append('file', file);
|
||||
form.append('model', sttModel);
|
||||
var fetchHeaders = {};
|
||||
if (process.env.LITELLM_API_KEY) fetchHeaders['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
|
||||
var sttResp = await fetch(gatewayUrl('/audio/transcriptions'), {
|
||||
method: 'POST', headers: fetchHeaders, body: form
|
||||
}).then(function(r) { return r.json().then(function(d) { return { data: d }; }); });
|
||||
var text = sttResp.data && sttResp.data.text ? sttResp.data.text : '';
|
||||
console.log('[Transcribe] LiteLLM/' + sttModel + ' done in ' + (Date.now() - startTime) + 'ms');
|
||||
logger.audit(req.user.id, 'transcribe', 'Transcribed audio via litellm', req, { category: 'clinical' });
|
||||
return res.json({ success: true, text: text.trim(), provider: 'litellm/' + sttModel, duration: Date.now() - startTime });
|
||||
}
|
||||
|
||||
// Gemini-style models — use /v1/chat/completions with inline audio
|
||||
var base64audio = req.file.buffer.toString('base64');
|
||||
var headers = { 'Content-Type': 'application/json' };
|
||||
if (process.env.LITELLM_API_KEY) headers['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
|
||||
var sttResp = await axios.post(gatewayUrl('/chat/completions'), {
|
||||
model: sttModel,
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'input_audio',
|
||||
input_audio: { data: base64audio, format: mimeType.split('/')[1] || 'webm' }
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Transcribe this audio of a medical encounter. Output the spoken words only, exactly as heard. No commentary, no formatting.'
|
||||
}
|
||||
]
|
||||
}]
|
||||
}, { headers: headers, timeout: 120000 });
|
||||
|
||||
var text = '';
|
||||
if (sttResp.data && sttResp.data.choices && sttResp.data.choices[0]) {
|
||||
var msg = sttResp.data.choices[0].message;
|
||||
text = (msg && msg.content) ? msg.content : '';
|
||||
var sttResp = await fetch(gatewayUrl('/audio/transcriptions'), {
|
||||
method: 'POST', headers: fetchHeaders, body: form,
|
||||
});
|
||||
if (!sttResp.ok) {
|
||||
var errBody = await sttResp.text();
|
||||
throw new Error('LiteLLM /audio/transcriptions ' + sttResp.status + ': ' + errBody.substring(0, 500));
|
||||
}
|
||||
var data = await sttResp.json();
|
||||
var text = (data && data.text) ? String(data.text).trim() : '';
|
||||
console.log('[Transcribe] LiteLLM/' + sttModel + ' done in ' + (Date.now() - startTime) + 'ms');
|
||||
logger.audit(req.user.id, 'transcribe', 'Transcribed audio via litellm', req, { category: 'clinical' });
|
||||
return res.json({ success: true, text: text.trim(), provider: 'litellm/' + sttModel, duration: Date.now() - startTime });
|
||||
return res.json({ success: true, text: text, provider: 'litellm/' + sttModel, duration: Date.now() - startTime });
|
||||
}
|
||||
|
||||
// OpenAI Whisper (direct)
|
||||
|
|
|
|||
|
|
@ -7,17 +7,21 @@ var logger = require('../utils/logger');
|
|||
var { gatewayUrl } = require('../utils/errors');
|
||||
|
||||
// Provider priority (auto-detect):
|
||||
// TTS_PROVIDER=google → Google Cloud TTS direct (HIPAA eligible)
|
||||
// TTS_PROVIDER=litellm → LiteLLM proxy
|
||||
// TTS_PROVIDER=elevenlabs → ElevenLabs (not HIPAA)
|
||||
// Auto: google > litellm > elevenlabs
|
||||
// TTS_PROVIDER=litellm → LiteLLM proxy at /v1/audio/speech (default when LITELLM_API_BASE is set)
|
||||
// TTS_PROVIDER=google → Google Cloud TTS direct (HIPAA eligible, opt-in)
|
||||
// TTS_PROVIDER=elevenlabs → ElevenLabs direct (not HIPAA, opt-in)
|
||||
// Auto: litellm > google > elevenlabs
|
||||
//
|
||||
// Design: the LiteLLM branch is a pure passthrough — model + voice strings
|
||||
// go straight into the OpenAI-compatible /v1/audio/speech body. No regex
|
||||
// dispatch on model/voice names; the LiteLLM config decides which provider
|
||||
// (OpenAI, ElevenLabs, Vertex, Azure) actually serves the request.
|
||||
function getTTSProvider() {
|
||||
var env = process.env.TTS_PROVIDER;
|
||||
if (env === 'google') return 'google';
|
||||
if (env === 'litellm') return 'litellm';
|
||||
if (env === 'elevenlabs') return 'elevenlabs';
|
||||
// Auto-detect: litellm > google > elevenlabs
|
||||
// LiteLLM Vertex TTS works correctly via alias (tts-1)
|
||||
if (process.env.LITELLM_API_BASE) return 'litellm';
|
||||
if (isGoogleTTSConfigured()) return 'google';
|
||||
if (process.env.ELEVENLABS_API_KEY) return 'elevenlabs';
|
||||
|
|
@ -39,15 +43,29 @@ router.post('/text-to-speech', authMiddleware, async (req, res) => {
|
|||
var userVoice = userPrefs?.tts_voice;
|
||||
var adminVoice = await db.getSetting('tts.voice') || '';
|
||||
var adminModel = await db.getSetting('tts.model') || '';
|
||||
// tts.voice may contain a model ID (from discovery "Set" button) — detect and use as model
|
||||
if (!adminModel && adminVoice && (adminVoice.indexOf('/') !== -1 || /^(openai-tts|tts-1|elevenlabs|vertex-gemini.*tts|openai-gpt.*tts)/i.test(adminVoice))) { adminModel = adminVoice; adminVoice = ''; }
|
||||
|
||||
// Auto-detect model from voice — ensures model matches the voice's provider
|
||||
var VERTEX_VOICES = ['Puck','Charon','Kore','Fenrir','Aoede','Leda','Orus','Zephyr'];
|
||||
var resolvedVoice = userVoice || adminVoice || '';
|
||||
if (resolvedVoice) {
|
||||
if (VERTEX_VOICES.indexOf(resolvedVoice) !== -1) adminModel = 'vertex-gemini-2.5-flash-tts';
|
||||
else if (resolvedVoice.length > 15 && /^[a-zA-Z0-9]+$/.test(resolvedVoice)) adminModel = 'elevenlabs-v3';
|
||||
|
||||
if (ttsProvider === 'litellm') {
|
||||
if (!process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' });
|
||||
var ttsModel = adminModel || process.env.LITELLM_TTS_MODEL || 'tts-1';
|
||||
var ttsVoice = resolvedVoice || process.env.LITELLM_TTS_VOICE || 'alloy';
|
||||
var headers = { 'Content-Type': 'application/json' };
|
||||
if (process.env.LITELLM_API_KEY) headers['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
|
||||
var ttsResp = await fetch(gatewayUrl('/audio/speech'), {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ model: ttsModel, input: text, voice: ttsVoice })
|
||||
});
|
||||
if (!ttsResp.ok) {
|
||||
var errBody = await ttsResp.text();
|
||||
throw new Error('LiteLLM /audio/speech ' + ttsResp.status + ': ' + errBody.substring(0, 500));
|
||||
}
|
||||
var audioBuf = Buffer.from(await ttsResp.arrayBuffer());
|
||||
res.set('Content-Type', ttsResp.headers.get('content-type') || 'audio/mpeg');
|
||||
res.set('X-TTS-Provider', 'litellm/' + ttsModel);
|
||||
logger.audit(req.user.id, 'text_to_speech', 'TTS generated', req, { category: 'clinical' });
|
||||
return res.send(audioBuf);
|
||||
}
|
||||
|
||||
if (ttsProvider === 'google') {
|
||||
|
|
@ -59,23 +77,6 @@ router.post('/text-to-speech', authMiddleware, async (req, res) => {
|
|||
return res.send(buffer);
|
||||
}
|
||||
|
||||
if (ttsProvider === 'litellm') {
|
||||
if (!process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' });
|
||||
var rawModel = adminModel || process.env.LITELLM_TTS_MODEL || 'tts-1';
|
||||
var ttsModel = rawModel.indexOf('/') !== -1 || rawModel.indexOf('-tts-') !== -1 || rawModel.startsWith('openai-') || rawModel.startsWith('elevenlabs-') || rawModel.startsWith('gemini-') || rawModel.startsWith('vertex-') ? rawModel : 'openai/' + rawModel;
|
||||
var ttsVoice = resolvedVoice || process.env.LITELLM_TTS_VOICE || 'alloy';
|
||||
var ttsHeaders = { 'Content-Type': 'application/json' };
|
||||
if (process.env.LITELLM_API_KEY) ttsHeaders['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
|
||||
var ttsResp = await axios.post(gatewayUrl('/audio/speech'),
|
||||
{ model: ttsModel, voice: ttsVoice, input: text },
|
||||
{ headers: ttsHeaders, responseType: 'arraybuffer', timeout: 60000 }
|
||||
);
|
||||
res.set('Content-Type', 'audio/mpeg');
|
||||
res.set('X-TTS-Provider', 'litellm/' + ttsModel);
|
||||
logger.audit(req.user.id, 'text_to_speech', 'TTS generated', req, { category: 'clinical' });
|
||||
return res.send(Buffer.from(ttsResp.data));
|
||||
}
|
||||
|
||||
if (ttsProvider === 'elevenlabs') {
|
||||
if (!process.env.ELEVENLABS_API_KEY) return res.status(400).json({ error: 'ElevenLabs not configured' });
|
||||
var resp = await axios({
|
||||
|
|
@ -91,7 +92,7 @@ router.post('/text-to-speech', authMiddleware, async (req, res) => {
|
|||
return res.send(Buffer.from(resp.data));
|
||||
}
|
||||
|
||||
res.status(400).json({ error: 'TTS not configured. Set GOOGLE_VERTEX_PROJECT, LITELLM_API_BASE, or ELEVENLABS_API_KEY.' });
|
||||
res.status(400).json({ error: 'TTS not configured. Set LITELLM_API_BASE, GOOGLE_VERTEX_PROJECT, or ELEVENLABS_API_KEY.' });
|
||||
} catch (err) {
|
||||
var detail = err.response && err.response.data
|
||||
? JSON.stringify(err.response.data).substring(0, 500)
|
||||
|
|
|
|||
Loading…
Reference in a new issue