@@ -1290,6 +1367,7 @@ const initApp = () => {
scrollToBottom();
} else if (parsed.type === 'video') {
isMediaResponse = true;
+ mediaMsgData = { type: 'video', url: parsed.url, originalPrompt: parsed.originalPrompt };
assistantContent = `
diff --git a/server.js b/server.js
index db6f4c2..dad867d 100644
--- a/server.js
+++ b/server.js
@@ -270,7 +270,7 @@ app.post('/api/chat', requireAuth, async (req, res) => {
let intent = 'TEXT';
// Executar classificação apenas se houver prompt de texto e não houver imagens anexadas
- if (!hasAttachedImages && promptText && process.env.MINIMAX_API_KEY) {
+ if (!hasAttachedImages && promptText && (process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY)) {
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.";
@@ -432,21 +432,127 @@ app.post('/api/chat', requireAuth, async (req, res) => {
// ==========================================================================
// FLUXO DE ÁUDIO/MÚSICA (Mensagem Pedagógica Amigável - MiniMax Desativado)
// ==========================================================================
+ // ==========================================================================
+ // FLUXO DE ÁUDIO/MÚSICA (google/lyria-3-clip-preview)
+ // ==========================================================================
if (intent === 'AUDIO') {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
- res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'A geração de músicas no momento está indisponível...' })}\n\n`);
+ res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Compondo e gerando música pedagógica com Lyria AI...' })}\n\n`);
try {
- const message = "A geração de músicas no momento está indisponível devido às limitações do plano. No entanto, se você quiser, eu posso compor uma linda letra de música infantil ou poesia pedagógica sobre o assunto para você! É só me dizer sobre o que quer falar.";
- res.write(`data: ${JSON.stringify({ content: message })}\n\n`);
+ if (!process.env.OPENROUTER_API_KEY) {
+ throw new Error('OPENROUTER_API_KEY não configurada no servidor.');
+ }
+
+ const response = 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: 'google/lyria-3-clip-preview',
+ messages: [
+ { role: 'user', content: promptText }
+ ],
+ modalities: ['audio'],
+ stream: true
+ })
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Erro na API do OpenRouter (Lyria): ${errorText}`);
+ }
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder('utf-8');
+ let buffer = '';
+ let audioBase64Chunks = [];
+ let textContent = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split('\n');
+ buffer = lines.pop();
+
+ for (const line of lines) {
+ const cleanLine = line.trim();
+ if (cleanLine.startsWith('data: ')) {
+ const dataContent = cleanLine.slice(6);
+ if (dataContent === '[DONE]') continue;
+ try {
+ const parsed = JSON.parse(dataContent);
+ const delta = parsed.choices?.[0]?.delta;
+ if (delta) {
+ if (delta.content) {
+ textContent += delta.content;
+ res.write(`data: ${JSON.stringify({ content: delta.content })}\n\n`);
+ }
+ if (delta.audio?.data) {
+ audioBase64Chunks.push(delta.audio.data);
+ }
+ }
+ } catch (e) {
+ // Ignorar
+ }
+ }
+ }
+ }
+
+ // Processar restante do buffer
+ if (buffer) {
+ const cleanLine = buffer.trim();
+ if (cleanLine.startsWith('data: ')) {
+ const dataContent = cleanLine.slice(6);
+ if (dataContent !== '[DONE]') {
+ try {
+ const parsed = JSON.parse(dataContent);
+ const delta = parsed.choices?.[0]?.delta;
+ if (delta) {
+ if (delta.content) {
+ textContent += delta.content;
+ res.write(`data: ${JSON.stringify({ content: delta.content })}\n\n`);
+ }
+ if (delta.audio?.data) {
+ audioBase64Chunks.push(delta.audio.data);
+ }
+ }
+ } catch (e) {
+ // Ignorar
+ }
+ }
+ }
+ }
+
+ if (audioBase64Chunks.length > 0) {
+ const fullAudioBase64 = audioBase64Chunks.join('');
+ const audioUrl = `data:audio/mp3;base64,${fullAudioBase64}`;
+ // Envia a mídia estruturada para o frontend
+ res.write(`data: ${JSON.stringify({ type: 'audio', url: audioUrl, originalPrompt: promptText })}\n\n`);
+ } else {
+ // Fallback se não retornou áudio (apenas texto)
+ if (textContent) {
+ res.write(`data: ${JSON.stringify({ content: '\n\n*(Nota: O áudio não pôde ser gerado por limitações do modelo, mas a letra acima foi composta com carinho!)*' })}\n\n`);
+ } else {
+ throw new Error('Nenhum dado de áudio ou texto foi retornado pelo modelo Lyria.');
+ }
+ }
+
res.write('data: [DONE]\n\n');
return res.end();
+
} catch (error) {
- console.error('Erro na resposta de áudio:', error);
- res.write(`data: ${JSON.stringify({ content: `Ocorreu um erro ao compor a música: ${error.message}` })}\n\n`);
+ console.error('Erro na resposta de áudio com Lyria:', error);
+ res.write(`data: ${JSON.stringify({ content: `Desculpe, ocorreu um erro ao gerar a música: ${error.message}. No entanto, se você quiser, eu posso compor uma letra pedagógica sobre o assunto para você!` })}\n\n`);
res.write('data: [DONE]\n\n');
return res.end();
}