Spaces:
Running
Running
| import express from 'express'; | |
| import cors from 'cors'; | |
| import { pipeline, env } from '@xenova/transformers'; | |
| import path from "path"; | |
| import { fileURLToPath } from 'url'; | |
| import textToSpeechAPI from './textToSpeech/textToSpeechAPI.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| const app = express(); | |
| app.use(cors()); | |
| app.use(express.json()); | |
| let extractor = null; | |
| let isInitializing = false; | |
| const init = async () => { | |
| if (extractor || isInitializing) return extractor; | |
| isInitializing = true; | |
| console.log('🔄 Inicializando E5...'); | |
| try { | |
| // Configurações para modelo local | |
| // env.allowRemoteModels = false; | |
| //env.allowLocalModels = true; | |
| env.useBrowserCache = false; | |
| // Define o caminho para a pasta de modelos | |
| //env.localModelPath = path.join(__dirname, 'models'); | |
| const modelPath = 'Xenova/multilingual-e5-small'; | |
| extractor = await pipeline('feature-extraction', modelPath, { | |
| quantized: true, | |
| device: 'cpu', | |
| local_files_only: false, | |
| }); | |
| console.log('✅ E5 carregado!'); | |
| return extractor; | |
| } catch (error) { | |
| console.error('❌ Erro:', error.message); | |
| throw error; | |
| } finally { | |
| isInitializing = false; | |
| } | |
| }; | |
| console.log('🔄 Inicializando TTS...'); | |
| textToSpeechAPI(app); | |
| console.log('✅ TTS carregado!'); | |
| // Health check mínimo | |
| app.get('/', (req, res) => { | |
| const uptimeSeconds = process.uptime(); // tempo desde que o servidor iniciou | |
| const uptime = `${Math.floor(uptimeSeconds / 60)} min ${Math.floor(uptimeSeconds % 60)} sec`; | |
| const now = new Date(); | |
| res.json({ | |
| status: extractor ? 'ready' : 'loading', | |
| model: 'e5-small', | |
| api_version: 'v1', | |
| timestamp: now.toISOString(), | |
| uptime: uptime, | |
| server_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, | |
| requester_ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress | |
| }); | |
| }); | |
| app.get('/ping', (req, res) => res.json({ pong: true })); | |
| // Endpoint principal otimizado | |
| app.post('/api/embed', async (req, res) => { | |
| try { | |
| if (!extractor) await init(); | |
| const { text, type = 'query' } = req.body; | |
| if (!text) return res.status(400).json({ error: 'Texto necessário' }); | |
| // Processa com configurações de baixa memória | |
| const result = await extractor([`${type}: ${text}`], { | |
| pooling: 'mean', | |
| normalize: true, | |
| // Reduz batch size para economizar RAM | |
| batch_size: 1, | |
| // Usa menos threads | |
| num_threads: 1 | |
| }); | |
| const embedding = Array.from(result[0].data); | |
| res.json({ embedding }); | |
| // Force garbage collection se disponível | |
| if (global.gc) global.gc(); | |
| } catch (err) { | |
| console.error('Erro embed:', err.message); | |
| res.status(500).json({ error: 'Erro interno' }); | |
| } | |
| }); | |
| // Batch otimizado para múltiplos textos | |
| app.post('/api/embed/batch', async (req, res) => { | |
| try { | |
| if (!extractor) await init(); | |
| const { texts, type = 'query' } = req.body; | |
| if (!Array.isArray(texts)) return res.status(400).json({ error: 'Array necessário' }); | |
| // Processa em chunks pequenos para não estourar memória | |
| const chunkSize = 3; | |
| const embeddings = []; | |
| for (let i = 0; i < texts.length; i += chunkSize) { | |
| const chunk = texts.slice(i, i + chunkSize); | |
| const prefixed = chunk.map(text => `${type}: ${text}`); | |
| const results = await extractor(prefixed, { | |
| pooling: 'mean', | |
| normalize: true, | |
| batch_size: 1, | |
| num_threads: 1 | |
| }); | |
| embeddings.push(...results.map(r => Array.from(r.data))); | |
| // Pequena pausa entre chunks | |
| await new Promise(resolve => setTimeout(resolve, 10)); | |
| } | |
| res.json({ | |
| embeddings, | |
| count: embeddings.length | |
| }); | |
| if (global.gc) global.gc(); | |
| } catch (err) { | |
| console.error('Erro batch:', err.message); | |
| res.status(500).json({ error: 'Erro interno' }); | |
| } | |
| }); | |
| const PORT = process.env.PORT || 7860; | |
| // Timeout agressivo | |
| const timeout = setTimeout(() => { | |
| if (isInitializing) { | |
| console.log('⚠️ Timeout - reiniciando'); | |
| process.exit(1); | |
| } | |
| }, 90000); // 90s | |
| // Inicializa o modelo automaticamente | |
| init().catch(console.error); | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`🚀 E5 na porta ${PORT}`); | |
| }); | |
| // Cleanup | |
| process.on('SIGTERM', () => { | |
| console.log('📴 Finalizando...'); | |
| process.exit(0); | |
| }); | |
| process.on('uncaughtException', (err) => { | |
| console.error('💥 Erro crítico:', err.message); | |
| process.exit(1); | |
| }); |