| const axios = require('axios'); |
| const { Readable } = require('stream'); |
| const { logger } = require('~/config'); |
| const getCustomConfig = require('~/server/services/Config/getCustomConfig'); |
| const { extractEnvVariable } = require('librechat-data-provider'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function handleResponse(response) { |
| if (response.status !== 200) { |
| throw new Error('Invalid response from the STT API'); |
| } |
|
|
| if (!response.data || !response.data.text) { |
| throw new Error('Missing data in response from the STT API'); |
| } |
|
|
| return response.data.text.trim(); |
| } |
|
|
| function getProvider(sttSchema) { |
| if (sttSchema.openai) { |
| return 'openai'; |
| } |
|
|
| throw new Error('Invalid provider'); |
| } |
|
|
| function removeUndefined(obj) { |
| Object.keys(obj).forEach((key) => { |
| if (obj[key] && typeof obj[key] === 'object') { |
| removeUndefined(obj[key]); |
| if (Object.keys(obj[key]).length === 0) { |
| delete obj[key]; |
| } |
| } else if (obj[key] === undefined) { |
| delete obj[key]; |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function openAIProvider(sttSchema, audioReadStream) { |
| try { |
| const url = sttSchema.openai?.url || 'https://api.openai.com/v1/audio/transcriptions'; |
| const apiKey = sttSchema.openai.apiKey ? extractEnvVariable(sttSchema.openai.apiKey) : ''; |
|
|
| let data = { |
| file: audioReadStream, |
| model: sttSchema.openai.model, |
| }; |
|
|
| let headers = { |
| 'Content-Type': 'multipart/form-data', |
| }; |
|
|
| [headers].forEach(removeUndefined); |
|
|
| if (apiKey) { |
| headers.Authorization = 'Bearer ' + apiKey; |
| } |
|
|
| return [url, data, headers]; |
| } catch (error) { |
| logger.error('An error occurred while preparing the OpenAI API STT request: ', error); |
| return [null, null, null]; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function azureProvider(req, audioReadStream) { |
| try { |
| const { endpoint } = req.body; |
| const azureConfig = req.app.locals[endpoint]; |
|
|
| if (!azureConfig) { |
| throw new Error(`No configuration found for endpoint: ${endpoint}`); |
| } |
|
|
| const { apiKey, instanceName, whisperModel, apiVersion } = Object.entries( |
| azureConfig.groupMap, |
| ).reduce((acc, [, value]) => { |
| if (acc) { |
| return acc; |
| } |
|
|
| const whisperKey = Object.keys(value.models).find((modelKey) => |
| modelKey.startsWith('whisper'), |
| ); |
|
|
| if (whisperKey) { |
| return { |
| apiVersion: value.version, |
| apiKey: value.apiKey, |
| instanceName: value.instanceName, |
| whisperModel: value.models[whisperKey]['deploymentName'], |
| }; |
| } |
|
|
| return null; |
| }, null); |
|
|
| if (!apiKey || !instanceName || !whisperModel || !apiVersion) { |
| throw new Error('Required Azure configuration values are missing'); |
| } |
|
|
| const baseURL = `https://${instanceName}.openai.azure.com`; |
|
|
| const url = `${baseURL}/openai/deployments/${whisperModel}/audio/transcriptions?api-version=${apiVersion}`; |
|
|
| let data = { |
| file: audioReadStream, |
| filename: 'audio.wav', |
| contentType: 'audio/wav', |
| knownLength: audioReadStream.length, |
| }; |
|
|
| const headers = { |
| ...data.getHeaders(), |
| 'Content-Type': 'multipart/form-data', |
| 'api-key': apiKey, |
| }; |
|
|
| return [url, data, headers]; |
| } catch (error) { |
| logger.error('An error occurred while preparing the Azure API STT request: ', error); |
| return [null, null, null]; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| async function speechToText(req, res) { |
| const customConfig = await getCustomConfig(); |
| if (!customConfig) { |
| return res.status(500).send('Custom config not found'); |
| } |
|
|
| if (!req.file || !req.file.buffer) { |
| return res.status(400).json({ message: 'No audio file provided in the FormData' }); |
| } |
|
|
| const audioBuffer = req.file.buffer; |
| const audioReadStream = Readable.from(audioBuffer); |
| audioReadStream.path = 'audio.wav'; |
|
|
| const provider = getProvider(customConfig.stt); |
|
|
| let [url, data, headers] = []; |
|
|
| switch (provider) { |
| case 'openai': |
| [url, data, headers] = openAIProvider(customConfig.stt, audioReadStream); |
| break; |
| case 'azure': |
| [url, data, headers] = azureProvider(req, audioReadStream); |
| break; |
| default: |
| throw new Error('Invalid provider'); |
| } |
|
|
| if (!Readable.from) { |
| const audioBlob = new Blob([audioBuffer], { type: req.file.mimetype }); |
| delete data['file']; |
| data['file'] = audioBlob; |
| } |
|
|
| try { |
| const response = await axios.post(url, data, { headers: headers }); |
| const text = await handleResponse(response); |
|
|
| res.json({ text }); |
| } catch (error) { |
| logger.error('An error occurred while processing the audio:', error); |
| res.sendStatus(500); |
| } |
| } |
|
|
| module.exports = speechToText; |
|
|