diff --git a/.env b/.env index 558f6c1..a89cdde 100644 --- a/.env +++ b/.env @@ -5,3 +5,5 @@ OPENROUTER_MODEL=openai/gpt-4.1-nano SESSION_SECRET=camila-secret-key-321123 MINIMAX_API_KEY=sk-cp-iWzSsdzjVy0tT5dnvv_sGrAlv3Nw7kkrZ8hPK0KjbNeSRQxIY1O6AhkQAq_yrYV1XPJY1TH6ZQeOlJZd-Zi_elkJhef9PcTSzn2m9bcKIxezarM19BREcVM MINIMAX_API_BASE=https://api.minimax.io/v1 +GROQ_API_KEY=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 +GROQ_MODEL=llama-3.3-70b-versatile diff --git a/server.js b/server.js index 2120221..afcbde2 100644 --- a/server.js +++ b/server.js @@ -274,24 +274,57 @@ app.post('/api/chat', requireAuth, async (req, res) => { try { const systemDetector = "Você é um classificador de intenções de prompt. Classifique a última mensagem do usuário em uma destas quatro categorias exatas: 'TEXT' (para conversas normais, perguntas, redação de relatórios, planejamentos, textos pedagógicos, respostas gerais), 'IMAGE' (se o usuário estiver pedindo explicitamente para gerar, desenhar, criar ou ilustrar uma imagem, figura, ilustração ou foto), 'AUDIO' (se o usuário estiver pedindo explicitamente para gerar uma música, melodia, trilha sonora, cantiga ou som), ou 'VIDEO' (se o usuário estiver pedindo explicitamente para gerar ou criar um vídeo ou animação). Responda APENAS com a palavra da categoria em letras maiúsculas: TEXT, IMAGE, AUDIO, VIDEO."; - const detectResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano', - messages: [ - { role: 'system', content: systemDetector }, - { role: 'user', content: promptText } - ], - max_tokens: 20, - temperature: 0.0 - }) - }); + let detectResponse; + let usingGroqForDetection = false; - if (detectResponse.ok) { + try { + detectResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano', + messages: [ + { role: 'system', content: systemDetector }, + { role: 'user', content: promptText } + ], + max_tokens: 20, + temperature: 0.0 + }) + }); + } catch (err) { + console.error('[Smart Router] Falha de conexão com OpenRouter para detecção, tentando Groq...', err.message); + } + + // Se OpenRouter falhou ou retornou erro (ex: status != 200), tenta o Groq como fallback + if ((!detectResponse || !detectResponse.ok) && process.env.GROQ_API_KEY) { + console.log('[Smart Router] Usando Groq (llama-3.1-8b-instant) para classificar intenção...'); + try { + detectResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.GROQ_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'llama-3.1-8b-instant', + messages: [ + { role: 'system', content: systemDetector }, + { role: 'user', content: promptText } + ], + max_tokens: 20, + temperature: 0.0 + }) + }); + usingGroqForDetection = detectResponse.ok; + } catch (groqErr) { + console.error('[Smart Router] Falha de detecção na API da Groq:', groqErr.message); + } + } + + if (detectResponse && detectResponse.ok) { const detectData = await detectResponse.json(); const detectedText = detectData.choices?.[0]?.message?.content?.toUpperCase() || 'TEXT'; console.log(`[Smart Router] Resposta crua do detector: "${detectedText}"`); @@ -615,31 +648,59 @@ app.post('/api/chat', requireAuth, async (req, res) => { } // ========================================================================== - // FLUXO PADRÃO DE TEXTO (OpenRouter com SSE streaming original) + // FLUXO PADRÃO DE TEXTO (OpenRouter com SSE streaming original, com fallback Groq) // ========================================================================== const enrichedMessages = [KEMILY_SYSTEM_PROMPT, ...messages]; try { - const openrouterUrl = 'https://openrouter.ai/api/v1/chat/completions'; - const response = await fetch(openrouterUrl, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, - 'HTTP-Referer': 'https://camila.reifonas.cloud', - 'X-Title': 'Camila ChatGPT replica', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: process.env.OPENROUTER_MODEL, - messages: enrichedMessages, - stream: true - }) - }); + let chatResponse; + let providerUsed = 'OpenRouter'; - if (!response.ok) { - const errorText = await response.text(); - console.error('Erro na API do OpenRouter:', errorText); - return res.status(response.status).json({ error: 'Erro ao processar requisição na API do OpenRouter' }); + try { + chatResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, + 'HTTP-Referer': 'https://camila.reifonas.cloud', + 'X-Title': 'Camila ChatGPT replica', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: process.env.OPENROUTER_MODEL, + messages: enrichedMessages, + stream: true + }) + }); + } catch (e) { + console.error('Erro na conexão com OpenRouter, tentando Groq...', e.message); + } + + // Se o OpenRouter falhar ou retornar erro (ex: status != 200), caímos no fallback do Groq + if ((!chatResponse || !chatResponse.ok) && process.env.GROQ_API_KEY) { + console.log('[Smart Router] Usando Groq (llama-3.3-70b-versatile) como fallback de streaming de texto...'); + providerUsed = 'Groq'; + try { + chatResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.GROQ_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile', + messages: enrichedMessages, + stream: true + }) + }); + } catch (groqErr) { + console.error('[Smart Router] Erro crítico: Falha no fallback do Groq também:', groqErr.message); + } + } + + if (!chatResponse || !chatResponse.ok) { + const errorText = chatResponse ? await chatResponse.text() : 'Sem conexão com os servidores de IA'; + console.error(`Erro na API de IA (${providerUsed}):`, errorText); + return res.status(500).json({ error: `Erro ao processar requisição na API de IA (${providerUsed})` }); } // Configurar headers para Server-Sent Events (SSE) @@ -647,7 +708,7 @@ app.post('/api/chat', requireAuth, async (req, res) => { res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); - const reader = response.body.getReader(); + const reader = chatResponse.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = '';