feat: implementa geracao de musica com google/lyria-3-clip-preview via OpenRouter
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user