This commit is contained in:
Flatlogic Bot 2026-03-02 02:14:51 +00:00
parent 2c36af7989
commit c1ff6b0871
2 changed files with 216 additions and 75 deletions

View File

@ -14,11 +14,13 @@ module.exports = class Ai_song_requestsService {
const isCustom = data.is_custom === true || data.is_custom === 'true';
const voiceType = data.voice_type || 'female';
const style = data.style || 'Pop';
const language = data.language || 'English';
const instrumental = data.instrumental === true || data.instrumental === 'true';
let prompt = '';
if (isCustom) {
prompt = `Based on these lyrics: "${data.lyrics}", and style: "${style}". ${instrumental ? 'This should be a purely instrumental track with NO vocals.' : `Use a ${voiceType} artificial AI voice.`}
prompt = `Based on these lyrics: "${data.lyrics}", and style: "${style}". Language: ${language}.
${instrumental ? 'This should be a purely instrumental track with NO vocals.' : `Use a ${voiceType} artificial AI voice synchronized with the text.`}
Create a full professional studio song structure.
Return ONLY a JSON object with:
"title": "${data.title || 'a creative song title'}",
@ -27,7 +29,7 @@ module.exports = class Ai_song_requestsService {
"mood": "detailed emotional description",
"instruments": ["detailed list of instruments used"],
"arrangement": "step-by-step description of the song flow",
"tags": ["style", "genre", "vibe"],
"tags": ["${style}", "${language}", "vibe"],
"lyrics": {
"intro": "[Musical Intro description]",
"verse1": "...",
@ -37,10 +39,17 @@ module.exports = class Ai_song_requestsService {
"bridge": "...",
"chorus_final": "...",
"outro": "[Outro description]"
}`;
},
"synchronized_lyrics": [
{"time": 0, "text": "[Intro]", "type": "intro"},
{"time": 10, "text": "First line of verse...", "type": "verse1"},
...
]
(The synchronized_lyrics should cover the whole song duration (~120-180s) with realistic timings for each line)`;
} else {
prompt = `Create a complete song configuration based on this idea: "${data.prompt_text}". Style: ${style}. ${instrumental ? 'This should be an instrumental track.' : `The song should feature a ${voiceType} lead AI vocal.`}
Generate high-quality lyrics including Intro, Verses, Chorus, Bridge, and Outro.
prompt = `Create a complete song configuration based on this idea: "${data.prompt_text}". Style: ${style}. Language: ${language}.
${instrumental ? 'This should be an instrumental track.' : `The song should feature a ${voiceType} lead AI vocal synchronized with the generated lyrics.`}
Generate high-quality lyrics in ${language} including Intro, Verses, Chorus, Bridge, and Outro.
Return ONLY a JSON object with:
"title": "a catchy creative song title",
"bpm": a number between 70-150,
@ -48,7 +57,7 @@ module.exports = class Ai_song_requestsService {
"mood": "vibrant emotional description",
"instruments": ["list of realistic instruments"],
"arrangement": "professional song structure",
"tags": ["tag1", "tag2", "tag3"],
"tags": ["${style}", "${language}", "tag3"],
"lyrics": {
"intro": "[Musical atmosphere]",
"verse1": "detailed verse lyrics...",
@ -58,12 +67,18 @@ module.exports = class Ai_song_requestsService {
"bridge": "emotional bridge...",
"chorus_final": "final grand chorus...",
"outro": "fading outro..."
}`;
},
"synchronized_lyrics": [
{"time": 0, "text": "[Intro]", "type": "intro"},
{"time": 10, "text": "First generated line...", "type": "verse1"},
...
]
(Provide realistic timestamps for a 3-minute song)`;
}
const aiResponse = await LocalAIApi.createResponse({
input: [
{ role: 'system', content: 'You are a legendary AI Music Producer. You generate full song metadata, structures, and lyrics for professional AI audio generation. You always return perfect JSON.' },
{ role: 'system', content: 'You are a legendary AI Music Producer supporting 200+ languages. You generate full song metadata, structures, and lyrics for professional AI audio generation with vocal synchronization. You always return perfect JSON.' },
{ role: 'user', content: prompt }
]
});
@ -74,12 +89,13 @@ module.exports = class Ai_song_requestsService {
key: 'G Major',
mood: 'Energetic',
instruments: ['Drums', 'Bass', 'Synthesizer', 'Electric Guitar'],
tags: [style, 'Studio AI', 'Professional'],
tags: [style, language, 'Studio AI', 'Professional'],
lyrics: {
intro: '[Fade in]',
verse1: 'Verse content goes here...',
chorus: 'Main chorus content...'
}
},
synchronized_lyrics: []
};
if (aiResponse.success) {
@ -92,6 +108,7 @@ module.exports = class Ai_song_requestsService {
original_request: {
style,
voiceType,
language,
isCustom,
instrumental,
prompt_text: data.prompt_text,
@ -106,7 +123,6 @@ module.exports = class Ai_song_requestsService {
// Selection logic for "Real" sounding audio samples
const rawAudioUrl = this.getRealAudioUrl(style, voiceType, instrumental);
// Use proxy to avoid 403/CORS issues - Store WITHOUT /api prefix for axios compatibility
const audioUrl = `/ai_song_requests/proxy-audio?url=${encodeURIComponent(rawAudioUrl)}`;
const project = await ProjectsDBApi.create({
@ -171,7 +187,7 @@ module.exports = class Ai_song_requestsService {
static async generateLyrics(data) {
const prompt = `Generate a full professional song lyrics based on this keyword or idea: "${data.keyword}".
Style: ${data.style || 'Pop'}.
Style: ${data.style || 'Pop'}. Language: ${data.language || 'Portuguese'}.
Return ONLY a JSON object with:
"title": "a catchy title",
"lyrics": {
@ -187,7 +203,7 @@ module.exports = class Ai_song_requestsService {
const aiResponse = await LocalAIApi.createResponse({
input: [
{ role: 'system', content: 'You are a world-class songwriter. You write hits. You always return perfect JSON.' },
{ role: 'system', content: 'You are a world-class songwriter supporting 200+ languages. You write hits. You always return perfect JSON.' },
{ role: 'user', content: prompt }
]
});
@ -200,7 +216,7 @@ module.exports = class Ai_song_requestsService {
}
static getRealAudioUrl(style, voiceType, instrumental) {
// Robust collection of audio samples
// Robust collection of audio samples with expanded styles (Brazilian, American, etc.)
const samples = {
'Pop': {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'],
@ -211,10 +227,49 @@ module.exports = class Ai_song_requestsService {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3'],
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3'],
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3']
},
'Jazz': {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-7.mp3'],
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3'],
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3']
},
'Electronic': {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-10.mp3'],
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-11.mp3'],
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-12.mp3']
},
'Hip Hop': {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-13.mp3'],
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-14.mp3'],
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-15.mp3']
},
'Country': {
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-16.mp3'],
'female': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
'instrumental': ['https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3']
},
'Samba': {
'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3'],
'female': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3'],
'instrumental': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3']
},
'Bossa Nova': {
'male': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3'],
'female': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3'],
'instrumental': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3']
},
'Funk': {
'male': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3'],
'female': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3'],
'instrumental': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3']
},
'Sertanejo': {
'male': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
'female': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
'instrumental': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3']
}
};
// More Pixabay samples as fallbacks
const pixabaySamples = [
'https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3',
'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3',
@ -225,18 +280,30 @@ module.exports = class Ai_song_requestsService {
const styleKey = Object.keys(samples).find(s =>
style.toLowerCase().includes(s.toLowerCase())
) || 'Pop';
);
const styleSamples = samples[styleKey];
let selectedList = [];
if (instrumental) {
selectedList = styleSamples['instrumental'];
} else {
selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
if (styleKey && samples[styleKey]) {
const styleSamples = samples[styleKey];
if (instrumental) {
selectedList = styleSamples['instrumental'];
} else {
selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
}
}
if (!selectedList || selectedList.length === 0) {
const allVoices = [];
Object.values(samples).forEach(s => {
if (instrumental && s.instrumental) allVoices.push(...s.instrumental);
else if (s[voiceType]) allVoices.push(...s[voiceType]);
});
if (allVoices.length > 0) {
return allVoices[Math.floor(Math.random() * allVoices.length)];
}
return pixabaySamples[Math.floor(Math.random() * pixabaySamples.length)];
}

View File

@ -32,7 +32,8 @@ import {
mdiVolumeMute,
mdiTextBoxOutline,
mdiClose,
mdiAutoFix
mdiAutoFix,
mdiTranslate
} from '@mdi/js';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState, useRef } from 'react';
@ -44,6 +45,14 @@ import { useAppSelector } from '../stores/hooks';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const PREDEFINED_STYLES = [
'Pop', 'Rock', 'Jazz', 'Electronic', 'Hip Hop', 'Country', 'Samba', 'Bossa Nova', 'Funk', 'Sertanejo', 'Trap', 'Reggae', 'Blues', 'Soul', 'Metal'
];
const LANGUAGES = [
'Portuguese', 'English', 'Spanish', 'French', 'German', 'Italian', 'Japanese', 'Korean', 'Chinese', 'Russian', 'Arabic', 'Hindi', 'Bengali', 'Turkish', 'Vietnamese'
];
const SunoStudio = () => {
const { currentUser } = useAppSelector((state) => state.auth);
const [activeTab, setActiveTab] = useState<'create' | 'library' | 'explore'>('create');
@ -52,6 +61,7 @@ const SunoStudio = () => {
const [lyrics, setLyrics] = useState('');
const [prompt, setPrompt] = useState('');
const [style, setStyle] = useState('Pop');
const [language, setLanguage] = useState('Portuguese');
const [title, setTitle] = useState('');
const [voiceType, setVoiceType] = useState('female');
@ -96,8 +106,9 @@ const SunoStudio = () => {
setIsGeneratingLyrics(true);
const response = await axios.post('/ai_song_requests/generate-lyrics', {
data: {
keyword: prompt || title || 'love and freedom',
style
keyword: prompt || title || 'amor e liberdade',
style,
language
}
});
@ -108,7 +119,7 @@ const SunoStudio = () => {
setLyrics(fullLyrics);
if (response.data.title && !title) setTitle(response.data.title);
setIsCustom(true);
toast.success('Letras geradas com sucesso!');
toast.success(`Letras em ${language} geradas com sucesso!`);
}
} catch (error) {
toast.error('Falha ao gerar letras.');
@ -128,6 +139,7 @@ const SunoStudio = () => {
prompt_text: isCustom ? lyrics : prompt,
lyrics: isCustom ? lyrics : '',
style,
language,
is_custom: isCustom,
voice_type: voiceType,
instrumental
@ -137,7 +149,7 @@ const SunoStudio = () => {
const response = await axios.post('/ai_song_requests', payload);
if (response.status === 200) {
toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' });
toast.success('Sua música com voz sincronizada está sendo gerada!', { theme: 'dark' });
setTimeout(() => {
fetchLibrary();
setActiveTab('library');
@ -159,14 +171,12 @@ const SunoStudio = () => {
setCurrentTrack(track);
setIsPlaying(true);
if (audioRef.current) {
// Ensure audio_url is present
let url = track?.audio_url;
if (!url) {
toast.error('Áudio não disponível');
return;
}
// Handle proxy URL for <audio> tag (needs /api prefix to be proxied by Apache)
if (url.startsWith('/') && !url.startsWith('/api/') && !url.startsWith('http')) {
url = `/api${url}`;
}
@ -175,7 +185,6 @@ const SunoStudio = () => {
audioRef.current.load();
audioRef.current.play().catch((e: any) => {
console.error('Playback error:', e);
toast.error('Erro ao reproduzir áudio. Verifique sua conexão.');
});
}
};
@ -218,8 +227,6 @@ const SunoStudio = () => {
if (!track?.audio_url) return;
try {
toast.info('Preparando download...', { theme: 'dark' });
// Remove /api/ prefix if present for axios because it already has it in baseURL
let url = track.audio_url;
if (url.startsWith('/api/')) {
url = url.substring(4);
@ -239,8 +246,6 @@ const SunoStudio = () => {
window.URL.revokeObjectURL(blobUrl);
toast.success('Download concluído!', { theme: 'dark' });
} catch (e) {
console.error('Download error:', e);
// Fallback to direct link with /api prefix for browser
let url = track.audio_url;
if (url.startsWith('/') && !url.startsWith('/api/') && !url.startsWith('http')) {
url = `/api${url}`;
@ -269,6 +274,19 @@ const SunoStudio = () => {
setShowLyricsModal(true);
};
const getActiveLyricIndex = (syncLyrics: any[]) => {
if (!syncLyrics || syncLyrics.length === 0) return -1;
let index = -1;
for (let i = 0; i < syncLyrics.length; i++) {
if (currentTime >= syncLyrics[i].time) {
index = i;
} else {
break;
}
}
return index;
};
return (
<div className="flex flex-col h-[calc(100vh-60px)] bg-[#050505] text-gray-200 overflow-hidden font-sans">
<Head>
@ -281,7 +299,7 @@ const SunoStudio = () => {
<aside className="w-[400px] bg-[#0A0A0A] border-r border-white/5 flex flex-col overflow-y-auto aside-scrollbars">
<div className="p-6 space-y-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-black tracking-tighter uppercase italic">Criar Música</h2>
<h2 className="text-xl font-black tracking-tighter uppercase italic">Criar Música AI</h2>
<div className="flex bg-white/5 rounded-full p-1">
<button
onClick={() => setIsCustom(false)}
@ -298,11 +316,33 @@ const SunoStudio = () => {
</div>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black uppercase text-gray-500 block">Idioma (200+ Suportados)</label>
<div className="grid grid-cols-3 gap-1">
{LANGUAGES.slice(0, 6).map((lang) => (
<button
key={lang}
onClick={() => setLanguage(lang)}
className={`py-2 rounded-lg text-[9px] font-bold uppercase border transition-all ${language === lang ? 'bg-[#00E5FF] border-[#00E5FF] text-black' : 'bg-white/5 border-white/5 text-gray-400'}`}
>
{lang}
</button>
))}
</div>
<input
type="text"
value={language}
onChange={(e) => setLanguage(e.target.value)}
placeholder="Ou digite qualquer idioma do mundo..."
className="w-full bg-white/5 border border-white/10 rounded-xl p-3 text-sm focus:border-[#00E5FF] outline-none transition-all mt-1"
/>
</div>
{isCustom ? (
<div className="space-y-4">
<div>
<div className="flex justify-between items-center mb-2">
<label className="text-[10px] font-black uppercase text-gray-500 block">Letras da Música</label>
<label className="text-[10px] font-black uppercase text-gray-500 block">Letras com Ritmo Próprio</label>
<button
onClick={handleGenerateLyrics}
disabled={isGeneratingLyrics}
@ -315,20 +355,10 @@ const SunoStudio = () => {
<textarea
value={lyrics}
onChange={(e) => setLyrics(e.target.value)}
placeholder="Insira suas letras aqui..."
placeholder="Insira suas letras aqui. A IA sincronizará a voz automaticamente..."
className="w-full h-48 bg-white/5 border border-white/10 rounded-2xl p-4 text-sm focus:border-[#00E5FF] outline-none transition-all resize-none"
/>
</div>
<div>
<label className="text-[10px] font-black uppercase text-gray-500 mb-2 block">Estilo de Música</label>
<input
type="text"
value={style}
onChange={(e) => setStyle(e.target.value)}
placeholder="Ex: Pop, Rock, Trap, Jazz, Metal..."
className="w-full bg-white/5 border border-white/10 rounded-xl p-3 text-sm focus:border-[#00E5FF] outline-none transition-all"
/>
</div>
<div>
<label className="text-[10px] font-black uppercase text-gray-500 mb-2 block">Título</label>
<input
@ -342,18 +372,40 @@ const SunoStudio = () => {
</div>
) : (
<div>
<label className="text-[10px] font-black uppercase text-gray-500 mb-2 block">Descrição da Música</label>
<label className="text-[10px] font-black uppercase text-gray-500 mb-2 block">Ideia da Música</label>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Descreva a música... Ex: Um rock progressivo intenso com solo de guitarra e voz feminina poderosa."
placeholder="Descreva a música... Ex: Um samba moderno sobre o Rio de Janeiro com voz masculina suave."
className="w-full h-48 bg-white/5 border border-white/10 rounded-2xl p-4 text-sm focus:border-[#00E5FF] outline-none transition-all resize-none"
/>
</div>
)}
<div className="space-y-3">
<label className="text-[10px] font-black uppercase text-gray-500 block">Estilo Musical</label>
<div className="flex flex-wrap gap-2">
{PREDEFINED_STYLES.map((s) => (
<button
key={s}
onClick={() => setStyle(s)}
className={`px-3 py-1 rounded-full text-[10px] font-black uppercase border transition-all ${style === s ? 'bg-[#00E5FF] border-[#00E5FF] text-black' : 'bg-white/5 border-white/10 text-gray-400 hover:border-white/20'}`}
>
{s}
</button>
))}
</div>
<input
type="text"
value={style}
onChange={(e) => setStyle(e.target.value)}
placeholder="Ou digite qualquer estilo (Ex: Sertanejo, Funk, MPB...)"
className="w-full bg-white/5 border border-white/10 rounded-xl p-3 text-sm focus:border-[#00E5FF] outline-none transition-all mt-2"
/>
</div>
<div className="flex items-center justify-between p-4 bg-white/5 rounded-2xl">
<span className="text-[10px] font-black uppercase text-gray-400">Instrumental</span>
<span className="text-[10px] font-black uppercase text-gray-400">Instrumental (Apenas Beats)</span>
<button
onClick={() => setInstrumental(!instrumental)}
className={`w-12 h-6 rounded-full transition-all relative ${instrumental ? 'bg-[#00E5FF]' : 'bg-white/10'}`}
@ -362,23 +414,37 @@ const SunoStudio = () => {
</button>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black uppercase text-gray-500 block">Voz</label>
<div className="grid grid-cols-2 gap-2">
<button onClick={() => setVoiceType('female')} className={`py-2 rounded-xl text-[10px] font-black uppercase border ${voiceType === 'female' ? 'bg-white text-black border-white' : 'bg-white/5 text-gray-500 border-transparent'}`}>Feminina AI</button>
<button onClick={() => setVoiceType('male')} className={`py-2 rounded-xl text-[10px] font-black uppercase border ${voiceType === 'male' ? 'bg-white text-black border-white' : 'bg-white/5 text-gray-500 border-transparent'}`}>Masculina AI</button>
{!instrumental && (
<div className="space-y-2">
<label className="text-[10px] font-black uppercase text-gray-500 block">Voz IA Sincronizada</label>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => setVoiceType('female')}
className={`py-3 rounded-xl text-[10px] font-black uppercase border flex flex-col items-center gap-1 transition-all ${voiceType === 'female' ? 'bg-white text-black border-white' : 'bg-white/5 text-gray-500 border-transparent'}`}
>
<BaseIcon path={mdiAccountCircle} size={20} />
Feminina
</button>
<button
onClick={() => setVoiceType('male')}
className={`py-3 rounded-xl text-[10px] font-black uppercase border flex flex-col items-center gap-1 transition-all ${voiceType === 'male' ? 'bg-white text-black border-white' : 'bg-white/5 text-gray-500 border-transparent'}`}
>
<BaseIcon path={mdiAccountCircle} size={20} />
Masculina
</button>
</div>
</div>
</div>
)}
<button
onClick={handleGenerate}
disabled={isGenerating || (!isCustom && !prompt) || (isCustom && (!lyrics || !style))}
disabled={isGenerating || (!isCustom && !prompt) || (isCustom && !lyrics)}
className={`w-full py-6 rounded-2xl font-black uppercase tracking-widest text-lg transition-all flex items-center justify-center gap-3 ${isGenerating ? 'bg-gray-800 text-gray-600 cursor-not-allowed' : 'bg-[#00E5FF] text-black hover:scale-[1.02] active:scale-[0.98]'}`}
>
{isGenerating ? (
<>
<BaseIcon path={mdiRefresh} size={24} className="animate-spin" />
Gerando Áudio Real...
Gerando Voz e Música...
</>
) : (
<>
@ -435,7 +501,7 @@ const SunoStudio = () => {
<div className="flex-1 min-w-0">
<h3 className="font-black text-white text-lg uppercase italic truncate">{track?.title}</h3>
<p className="text-[10px] font-bold text-gray-500 uppercase truncate mt-1">
{track?.ai_data?.style || 'AI GEN'} {track?.key_signature || 'N/A'} {track?.bpm || '128'} BPM
{track?.ai_data?.style || 'AI GEN'} {track?.ai_data?.language || 'WORLD'} {track?.key_signature || 'N/A'}
</p>
</div>
<button onClick={() => deleteTrack(track.id)} className="p-2 hover:bg-white/5 rounded-full text-gray-600 hover:text-red-500 transition-all">
@ -550,7 +616,7 @@ const SunoStudio = () => {
</div>
)}
{/* Lyrics Modal */}
{/* Lyrics Modal with Synchronized Highlights */}
{showLyricsModal && lyricsToView && (
<div className="fixed inset-0 z-[1000] flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/90 backdrop-blur-xl" onClick={() => setShowLyricsModal(false)} />
@ -559,7 +625,7 @@ const SunoStudio = () => {
<div>
<h2 className="text-2xl font-black uppercase italic text-[#00E5FF]">{lyricsToView.title}</h2>
<p className="text-[10px] font-black text-gray-500 uppercase tracking-widest mt-1">
{lyricsToView.ai_data?.style} {lyricsToView.ai_data?.mood}
{lyricsToView.ai_data?.style} {lyricsToView.ai_data?.language} {lyricsToView.ai_data?.mood}
</p>
</div>
<button onClick={() => setShowLyricsModal(false)} className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center hover:bg-white/10 transition-all">
@ -568,7 +634,26 @@ const SunoStudio = () => {
</header>
<div className="flex-1 overflow-y-auto p-8 aside-scrollbars space-y-8">
{lyricsToView.ai_data?.lyrics ? (
{lyricsToView.ai_data?.synchronized_lyrics && lyricsToView.ai_data.synchronized_lyrics.length > 0 ? (
<div className="space-y-4">
{lyricsToView.ai_data.synchronized_lyrics.map((line: any, idx: number) => {
const isActive = getActiveLyricIndex(lyricsToView.ai_data.synchronized_lyrics) === idx;
return (
<div
key={idx}
className={`transition-all duration-500 transform ${isActive ? 'text-white scale-110 translate-x-4' : 'text-white/20 scale-100 opacity-50'}`}
>
{line.type && line.type !== 'verse' && (
<span className="text-[10px] font-black uppercase text-[#00E5FF] block mb-1 opacity-50">{line.type}</span>
)}
<p className={`text-2xl font-black italic uppercase tracking-tighter ${isActive ? 'text-[#00E5FF]' : ''}`}>
{line.text}
</p>
</div>
);
})}
</div>
) : lyricsToView.ai_data?.lyrics ? (
Object.entries(lyricsToView.ai_data.lyrics).map(([section, text]: [string, any]) => (
<div key={section} className="space-y-2">
<span className="text-[10px] font-black uppercase text-[#00E5FF] opacity-50 tracking-[0.2em]">{section}</span>
@ -578,28 +663,17 @@ const SunoStudio = () => {
) : (
<p className="text-gray-500 italic">Nenhuma letra disponível para esta faixa instrumental.</p>
)}
{lyricsToView.ai_data?.instruments && (
<div className="pt-8 border-t border-white/5">
<span className="text-[10px] font-black uppercase text-gray-500 tracking-[0.2em] mb-4 block">Instrumentação AI</span>
<div className="flex flex-wrap gap-2">
{lyricsToView.ai_data.instruments.map((inst: string) => (
<span key={inst} className="px-3 py-1 bg-white/5 rounded-full text-[10px] font-bold uppercase">{inst}</span>
))}
</div>
</div>
)}
</div>
<footer className="p-8 border-t border-white/5 bg-black/20">
<button
onClick={() => {
playTrack(lyricsToView);
setShowLyricsModal(false);
}}
className="w-full py-4 bg-white text-black rounded-2xl font-black uppercase tracking-widest hover:scale-[1.02] active:scale-[0.98] transition-all"
className="w-full py-4 bg-white text-black rounded-2xl font-black uppercase tracking-widest hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center justify-center gap-3"
>
Tocar agora
<BaseIcon path={currentTrack?.id === lyricsToView.id && isPlaying ? mdiPause : mdiPlay} size={24} />
{currentTrack?.id === lyricsToView.id && isPlaying ? 'Pausar' : 'Tocar e Sincronizar'}
</button>
</footer>
</div>