This repository has been archived on 2026-05-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
closed-caption-app/src/server/index.js
T

271 lines
7.6 KiB
JavaScript

require('dotenv').config();
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const path = require('path');
const multer = require('multer');
const whisperBackend = require('./backends/whisper');
const elevenLabsBackend = require('./backends/elevenlabs');
const settingsManager = require('./utils/settingsManager');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const upload = multer({ storage: multer.memoryStorage() });
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, '../../public')));
const activeBackend = {
current: 'whisper-local',
instance: null
};
// Initialize settings
settingsManager.loadSettings().then(() => {
settingsManager.applyToEnv();
activeBackend.current = settingsManager.getSetting('selectedBackend') || 'whisper-local';
console.log('Settings loaded, active backend:', activeBackend.current);
});
app.post('/api/backend', (req, res) => {
const { backend } = req.body;
activeBackend.current = backend;
console.log(`Switched to backend: ${backend}`);
res.json({ success: true, backend });
});
app.post('/api/transcribe', upload.single('audio'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No audio file provided' });
}
let transcription = '';
switch (activeBackend.current) {
case 'whisper-local':
transcription = await whisperBackend.transcribeLocal(req.file.buffer);
break;
case 'whisper-remote':
transcription = await whisperBackend.transcribeRemote(req.file.buffer);
break;
case 'elevenlabs':
transcription = await elevenLabsBackend.transcribe(req.file.buffer);
break;
default:
throw new Error(`Unknown backend: ${activeBackend.current}`);
}
// Only emit non-empty transcriptions
if (transcription && transcription.trim()) {
io.emit('transcription', { text: transcription, timestamp: Date.now() });
}
res.json({ transcription });
} catch (error) {
console.error('Transcription error:', error);
res.status(500).json({ error: error.message });
}
});
app.get('/api/backends', (req, res) => {
res.json({
backends: [
{ id: 'whisper-local', name: 'Whisper (Local)', available: true },
{ id: 'whisper-remote', name: 'Whisper (OpenAI API)', available: !!process.env.OPENAI_API_KEY },
{ id: 'elevenlabs', name: 'ElevenLabs (Scribe)', available: !!process.env.ELEVENLABS_API_KEY }
],
current: activeBackend.current
});
});
app.get('/api/queue-status', (req, res) => {
try {
const status = whisperBackend.getQueueStatus();
res.json(status);
} catch (error) {
res.status(500).json({ error: 'Failed to get queue status' });
}
});
// Settings endpoints
app.get('/api/settings', (req, res) => {
try {
const settings = settingsManager.getSettings();
// Don't send API keys to client for security
const clientSettings = { ...settings };
if (clientSettings.openaiApiKey) {
clientSettings.openaiApiKey = '***hidden***';
}
if (clientSettings.elevenlabsApiKey) {
clientSettings.elevenlabsApiKey = '***hidden***';
}
res.json(clientSettings);
} catch (error) {
res.status(500).json({ error: 'Failed to load settings' });
}
});
app.post('/api/settings', async (req, res) => {
try {
const updates = req.body;
// Only update API keys if they're not the hidden placeholder
if (updates.openaiApiKey === '***hidden***') {
delete updates.openaiApiKey;
}
if (updates.elevenlabsApiKey === '***hidden***') {
delete updates.elevenlabsApiKey;
}
const success = await settingsManager.updateSettings(updates);
if (success) {
// Reload settings to ensure they're current
await settingsManager.loadSettings();
settingsManager.applyToEnv();
// Update active backend if changed
if (updates.selectedBackend) {
activeBackend.current = updates.selectedBackend;
}
// Log GPU changes for debugging
if (updates.gpuAcceleration !== undefined) {
console.log(`GPU acceleration changed to: ${updates.gpuAcceleration}`);
}
if (updates.whisperModel !== undefined) {
console.log(`Whisper model changed to: ${updates.whisperModel}`);
}
res.json({ success: true });
} else {
res.status(500).json({ error: 'Failed to save settings' });
}
} catch (error) {
console.error('Error updating settings:', error);
res.status(500).json({ error: 'Failed to save settings' });
}
});
app.post('/api/settings/reset', async (req, res) => {
try {
const success = await settingsManager.resetToDefaults();
if (success) {
settingsManager.applyToEnv();
activeBackend.current = 'whisper-local';
res.json({ success: true });
} else {
res.status(500).json({ error: 'Failed to reset settings' });
}
} catch (error) {
console.error('Error resetting settings:', error);
res.status(500).json({ error: 'Failed to reset settings' });
}
});
app.post('/api/detect-gpu', async (req, res) => {
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const result = {
cuda: false,
rocm: false,
openvino: false,
recommended: 'none'
};
try {
// Check for NVIDIA GPU (CUDA)
try {
await execPromise('nvidia-smi');
result.cuda = true;
result.recommended = 'cuda';
} catch (e) {
// NVIDIA not available
}
// Check for AMD GPU (ROCm)
try {
await execPromise('rocm-smi');
result.rocm = true;
if (!result.cuda) {
result.recommended = 'rocm';
}
} catch (e) {
// ROCm not available
}
// Check for Intel GPU/OpenVINO
try {
const { stdout } = await execPromise('lspci | grep -i intel | grep -i vga || echo ""');
if (stdout.includes('Intel')) {
result.openvino = true;
if (!result.cuda && !result.rocm) {
result.recommended = 'openvino';
}
}
} catch (e) {
// Intel GPU check failed
}
res.json(result);
} catch (error) {
console.error('GPU detection error:', error);
res.status(500).json({ error: 'Failed to detect GPU' });
}
});
app.post('/api/test-connections', async (req, res) => {
const { openaiApiKey, elevenlabsApiKey } = req.body;
const results = {};
// Test OpenAI connection
if (openaiApiKey && openaiApiKey !== '***hidden***') {
try {
const OpenAI = require('openai');
const openai = new OpenAI({ apiKey: openaiApiKey });
await openai.models.list();
results.openai = { success: true };
} catch (error) {
results.openai = { success: false, error: error.message };
}
}
// Test ElevenLabs connection
if (elevenlabsApiKey && elevenlabsApiKey !== '***hidden***') {
try {
const axios = require('axios');
await axios.get('https://api.elevenlabs.io/v1/user', {
headers: { 'xi-api-key': elevenlabsApiKey }
});
results.elevenlabs = { success: true };
} catch (error) {
results.elevenlabs = { success: false, error: error.response?.data?.detail?.message || error.message };
}
}
res.json(results);
});
io.on('connection', (socket) => {
console.log('Client connected');
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
const PORT = process.env.SERVER_PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});