diff --git a/public/components/admin.html b/public/components/admin.html index 564a0b7e..9a13858c 100644 --- a/public/components/admin.html +++ b/public/components/admin.html @@ -10,12 +10,45 @@
API Calls Today
- +

Registration

-
- Loading... - +
+
+ Loading... + +
+ +
+ Invite only +
+ +

Sits between open and closed registration. With registration disabled entirely, nobody can register even with a code.

+
+
+ +
+ +
+ + + +
+
+

The code is shown once, here. Only its hash is stored, so it cannot be read again afterwards.

+
+ +
+
@@ -385,40 +418,6 @@
- -
-
-

Registration Invitations

-
-
-
- Invite only -
- -

Sits between open and closed registration. With registration disabled entirely, nobody can register even with a code.

-
-
- -
- -
- - - -
-
-

The code is shown once, here. Only its hash is stored, so it cannot be read again afterwards.

-
- -
-
-
-
diff --git a/src/routes/transcribe.js b/src/routes/transcribe.js index a2b4c387..8743cf56 100644 --- a/src/routes/transcribe.js +++ b/src/routes/transcribe.js @@ -48,7 +48,10 @@ router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, r var sttModel = userModel || adminSttModel || process.env.LITELLM_STT_MODEL || ''; if (!sttModel) return res.status(400).json({ error: 'No LiteLLM STT model configured.' }); var mimeType = req.file.mimetype || 'audio/webm'; - var ext = mimeType.split('/')[1] || 'webm'; + // Browsers report "audio/webm;codecs=opus", and splitting on "/" alone + // produced the filename "audio.webm;codecs=opus". This gateway tolerates it, + // but a provider that dispatches on file extension would not. + var ext = (mimeType.split('/')[1] || 'webm').split(';')[0].trim() || 'webm'; // Every recording is kept for 24 hours, not only the ones that fail. The // audio is already here, so this costs no extra upload — and it means a @@ -78,6 +81,19 @@ router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, r 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'); + // A model can answer 200 with no words at all for audio that plainly + // contains speech — mistral-voxtral-mini-transcribe did exactly that for + // every recording from one microphone while transcribing clean synthetic + // speech perfectly. That looked like "recording lost" from the outside and + // took a day of digging to attribute. Anything larger than a moment of + // audio coming back empty is a fact about the model, so say so here. + if (!text && fileSize > 8 * 1024) { + console.warn('[Transcribe] ' + sttModel + ' returned no text for ' + + (fileSize / 1024).toFixed(0) + 'KB of ' + mimeType + + ' — if this repeats, the audio is probably fine and the model is dropping it. ' + + 'The recording is kept for 24 hours (backup id ' + (backupId == null ? 'none' : backupId) + + '); try another stt.model against it before suspecting the microphone.'); + } logger.audit(req.user.id, 'transcribe', 'Transcribed audio via litellm', req, { category: 'clinical' }); return res.json({ success: true, text: text, provider: 'litellm/' + sttModel, duration: Date.now() - startTime, backupId: backupId });