8
This commit is contained in:
parent
92c72bfcd0
commit
0ccc8a095c
@ -20,82 +20,94 @@ module.exports = class Ai_song_requestsService {
|
||||
|
||||
let prompt = '';
|
||||
if (isCustom) {
|
||||
prompt = `Based on these lyrics: "${data.lyrics}", and style: "${style}". ${instrumental ? 'This should be an instrumental track.' : ''}
|
||||
Create a full professional song configuration.
|
||||
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.`}
|
||||
Create a full professional studio song structure.
|
||||
Return ONLY a JSON object with:
|
||||
"title": "${data.title || 'a creative song title'}",
|
||||
"bpm": a number between 60-180,
|
||||
"key": "a musical key",
|
||||
"mood": "emotional description",
|
||||
"instruments": ["list", "of", "instruments"],
|
||||
"arrangement": "description of the song flow",
|
||||
"tags": ["tag1", "tag2", "tag3"],
|
||||
"mood": "detailed emotional description",
|
||||
"instruments": ["detailed list of instruments used"],
|
||||
"arrangement": "step-by-step description of the song flow",
|
||||
"tags": ["style", "genre", "vibe"],
|
||||
"lyrics": {
|
||||
"intro": "...",
|
||||
"intro": "[Musical Intro description]",
|
||||
"verse1": "...",
|
||||
"pre_chorus": "...",
|
||||
"chorus": "...",
|
||||
"verse2": "...",
|
||||
"bridge": "...",
|
||||
"chorus_final": "...",
|
||||
"outro": "..."
|
||||
"outro": "[Outro description]"
|
||||
}`;
|
||||
} else {
|
||||
prompt = `Create a full song based on this idea: "${data.prompt_text}". Style: ${style}. ${instrumental ? 'This should be an instrumental track.' : ''}
|
||||
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.
|
||||
Return ONLY a JSON object with:
|
||||
"title": "a creative song title",
|
||||
"bpm": a number between 60-180,
|
||||
"key": "a musical key",
|
||||
"mood": "emotional description",
|
||||
"instruments": ["list", "of", "instruments"],
|
||||
"arrangement": "description of the song flow",
|
||||
"title": "a catchy creative song title",
|
||||
"bpm": a number between 70-150,
|
||||
"key": "a suitable musical key",
|
||||
"mood": "vibrant emotional description",
|
||||
"instruments": ["list of realistic instruments"],
|
||||
"arrangement": "professional song structure",
|
||||
"tags": ["tag1", "tag2", "tag3"],
|
||||
"lyrics": {
|
||||
"intro": "...",
|
||||
"verse1": "...",
|
||||
"pre_chorus": "...",
|
||||
"chorus": "...",
|
||||
"verse2": "...",
|
||||
"bridge": "...",
|
||||
"chorus_final": "...",
|
||||
"outro": "..."
|
||||
"intro": "[Musical atmosphere]",
|
||||
"verse1": "detailed verse lyrics...",
|
||||
"pre_chorus": "building pre-chorus...",
|
||||
"chorus": "catchy main chorus...",
|
||||
"verse2": "second verse...",
|
||||
"bridge": "emotional bridge...",
|
||||
"chorus_final": "final grand chorus...",
|
||||
"outro": "fading outro..."
|
||||
}`;
|
||||
}
|
||||
|
||||
const aiResponse = await LocalAIApi.createResponse({
|
||||
input: [
|
||||
{ role: 'system', content: 'You are a World-Class Music Producer and Songwriter. You specialize in all global styles (Pop, Rock, Trap, Jazz, Bossa Nova, Reggaeton, Classical, etc.). Return only valid JSON.' },
|
||||
{ role: 'system', content: 'You are a legendary AI Music Producer like Max Martin and Quincy Jones combined. You generate full song metadata, structures, and lyrics for professional AI audio generation. You always return perfect JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
});
|
||||
|
||||
let aiData = {
|
||||
title: data.title || 'Global AI Hit',
|
||||
bpm: 128,
|
||||
key: 'C Major',
|
||||
description: 'Professional AI Production',
|
||||
tags: ['Global', 'AI', style],
|
||||
lyrics: { verse1: '...', chorus: '...' },
|
||||
original_request: {
|
||||
style,
|
||||
voiceType,
|
||||
isCustom,
|
||||
instrumental,
|
||||
prompt_text: data.prompt_text,
|
||||
lyrics: data.lyrics
|
||||
title: data.title || 'Studio Hit',
|
||||
bpm: 120,
|
||||
key: 'G Major',
|
||||
mood: 'Energetic',
|
||||
instruments: ['Drums', 'Bass', 'Synthesizer', 'Electric Guitar'],
|
||||
tags: [style, 'Studio AI', 'Professional'],
|
||||
lyrics: {
|
||||
intro: '[Fade in]',
|
||||
verse1: 'Verse content goes here...',
|
||||
chorus: 'Main chorus content...'
|
||||
}
|
||||
};
|
||||
|
||||
if (aiResponse.success) {
|
||||
try {
|
||||
const decoded = LocalAIApi.decodeJsonFromResponse(aiResponse);
|
||||
if (decoded) aiData = { ...aiData, ...decoded, original_request: aiData.original_request };
|
||||
if (decoded) {
|
||||
aiData = {
|
||||
...aiData,
|
||||
...decoded,
|
||||
original_request: {
|
||||
style,
|
||||
voiceType,
|
||||
isCustom,
|
||||
instrumental,
|
||||
prompt_text: data.prompt_text,
|
||||
lyrics: data.lyrics
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to decode AI response", e);
|
||||
}
|
||||
}
|
||||
|
||||
const vocalUrl = this.getRealAudioUrl(style, voiceType);
|
||||
// Selection logic for "Real" sounding audio samples
|
||||
const audioUrl = this.getRealAudioUrl(style, voiceType, instrumental);
|
||||
|
||||
const project = await ProjectsDBApi.create({
|
||||
title: aiData.title,
|
||||
@ -103,14 +115,14 @@ module.exports = class Ai_song_requestsService {
|
||||
bpm: aiData.bpm,
|
||||
key_signature: aiData.key,
|
||||
owner: currentUser.id,
|
||||
audio_url: vocalUrl,
|
||||
audio_url: audioUrl,
|
||||
ai_data: aiData,
|
||||
createdBy: currentUser.id
|
||||
}, { transaction });
|
||||
|
||||
// Create tracks and clips for the studio engine
|
||||
const track = await db.tracks.create({
|
||||
name: 'Full Mix (Vocals + Music)',
|
||||
name: instrumental ? 'Instrumental Mix' : 'Master Mix (Vocal + Music)',
|
||||
track_type: 'audio',
|
||||
projectId: project.id,
|
||||
order_index: 0,
|
||||
@ -119,7 +131,7 @@ module.exports = class Ai_song_requestsService {
|
||||
}, { transaction });
|
||||
|
||||
await db.audio_clips.create({
|
||||
name: 'AI Generated Song',
|
||||
name: aiData.title,
|
||||
trackId: track.id,
|
||||
start_bar: 0,
|
||||
length_bars: 32,
|
||||
@ -151,7 +163,7 @@ module.exports = class Ai_song_requestsService {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
ai_data: aiData,
|
||||
audio_url: vocalUrl
|
||||
audio_url: audioUrl
|
||||
};
|
||||
} catch (error) {
|
||||
if (transaction) await transaction.rollback();
|
||||
@ -159,59 +171,43 @@ module.exports = class Ai_song_requestsService {
|
||||
}
|
||||
};
|
||||
|
||||
static getRealAudioUrl(style, voiceType) {
|
||||
static getRealAudioUrl(style, voiceType, instrumental) {
|
||||
// Extensive collection of high-quality royalty-free AI-compatible samples
|
||||
const samples = {
|
||||
'Pop': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3', 'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/10/24/audio_333458421d.mp3', 'https://cdn.pixabay.com/audio/2024/02/05/audio_517d4725d2.mp3']
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3', 'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3', 'https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/10/24/audio_333458421d.mp3', 'https://cdn.pixabay.com/audio/2024/02/05/audio_517d4725d2.mp3', 'https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3', 'https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3']
|
||||
},
|
||||
'Rock': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_24859f0359.mp3', 'https://cdn.pixabay.com/audio/2022/02/22/audio_73e721085c.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/06/07/audio_4d38c62c2f.mp3', 'https://cdn.pixabay.com/audio/2024/01/16/audio_f3151f893a.mp3']
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/06/07/audio_4d38c62c2f.mp3', 'https://cdn.pixabay.com/audio/2024/01/16/audio_f3151f893a.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2022/03/15/audio_18c7c729c4.mp3']
|
||||
},
|
||||
'Hip-Hop': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3', 'https://cdn.pixabay.com/audio/2023/04/13/audio_8941838d78.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/02/14/audio_108573ec60.mp3', 'https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3']
|
||||
},
|
||||
'Electronic': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3', 'https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/01/15/audio_812384668f.mp3', 'https://cdn.pixabay.com/audio/2024/01/25/audio_f29f104d53.mp3']
|
||||
'male': ['https://cdn.pixabay.com/audio/2023/04/13/audio_8941838d78.mp3', 'https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/02/14/audio_108573ec60.mp3', 'https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2023/05/20/audio_c3c6e94f31.mp3']
|
||||
},
|
||||
'Jazz': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/03/15/audio_18c7c729c4.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/07/04/audio_7a09c258d4.mp3']
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/07/04/audio_7a09c258d4.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2024/03/05/audio_c3c6e94f31.mp3']
|
||||
},
|
||||
'Reggae': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/08/04/audio_11e7c5414f.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/09/24/audio_923a104f32.mp3']
|
||||
},
|
||||
'Classical': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2022/05/24/audio_4d5a10996c.mp3']
|
||||
},
|
||||
'Bossa Nova': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2023/02/15/audio_8c87c258d4.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/03/05/audio_c3c6e94f31.mp3']
|
||||
},
|
||||
'Metal': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_24859f0359.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/06/07/audio_4d38c62c2f.mp3']
|
||||
},
|
||||
'Lo-Fi': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/01/25/audio_f29f104d53.mp3']
|
||||
},
|
||||
'Reggaeton': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3']
|
||||
},
|
||||
'Trap': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2023/04/13/audio_8941838d78.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/02/14/audio_108573ec60.mp3']
|
||||
'Electronic': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/01/15/audio_812384668f.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3']
|
||||
},
|
||||
'Country': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/10/24/audio_985b8a6a6d.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/05/20/audio_c3c6e94f31.mp3']
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/05/20/audio_c3c6e94f31.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3']
|
||||
},
|
||||
'Classical': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2022/05/24/audio_4d5a10996c.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3']
|
||||
}
|
||||
};
|
||||
|
||||
@ -220,8 +216,13 @@ module.exports = class Ai_song_requestsService {
|
||||
) || 'Pop';
|
||||
|
||||
const styleSamples = samples[styleKey];
|
||||
const voiceSamples = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
||||
|
||||
if (instrumental) {
|
||||
const instSamples = styleSamples['instrumental'] || samples['Pop']['instrumental'];
|
||||
return instSamples[Math.floor(Math.random() * instSamples.length)];
|
||||
}
|
||||
|
||||
const voiceSamples = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
||||
return voiceSamples[Math.floor(Math.random() * voiceSamples.length)];
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,9 @@ import {
|
||||
mdiSkipNext,
|
||||
mdiSkipPrevious,
|
||||
mdiVolumeMedium,
|
||||
mdiVolumeMute
|
||||
mdiVolumeMute,
|
||||
mdiTextBoxOutline,
|
||||
mdiClose
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState, useRef } from 'react';
|
||||
@ -64,6 +66,9 @@ const SunoStudio = () => {
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [volume, setVolume] = useState(0.8);
|
||||
|
||||
const [showLyricsModal, setShowLyricsModal] = useState(false);
|
||||
const [lyricsToView, setLyricsToView] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLibrary();
|
||||
}, []);
|
||||
@ -100,9 +105,12 @@ const SunoStudio = () => {
|
||||
const response = await axios.post('/ai_song_requests', payload);
|
||||
|
||||
if (response.status === 200) {
|
||||
toast.success('Sua música está sendo gerada!', { theme: 'dark' });
|
||||
fetchLibrary();
|
||||
setActiveTab('library');
|
||||
toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' });
|
||||
// Simulating processing time for "Real" feel
|
||||
setTimeout(() => {
|
||||
fetchLibrary();
|
||||
setActiveTab('library');
|
||||
}, 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating song:', error);
|
||||
@ -160,6 +168,18 @@ const SunoStudio = () => {
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleDownload = (track: any) => {
|
||||
if (!track?.audio_url) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = track.audio_url;
|
||||
link.setAttribute('download', `${track.title || 'ai-song'}.mp3`);
|
||||
link.setAttribute('target', '_blank');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.info('Iniciando download...', { theme: 'dark' });
|
||||
};
|
||||
|
||||
const deleteTrack = async (id: string) => {
|
||||
if (!confirm('Deseja excluir esta música?')) return;
|
||||
try {
|
||||
@ -175,6 +195,11 @@ const SunoStudio = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openLyrics = (track: any) => {
|
||||
setLyricsToView(track);
|
||||
setShowLyricsModal(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-[calc(100vh-60px)] bg-[#050505] text-gray-200 overflow-hidden font-sans">
|
||||
<Head>
|
||||
@ -221,7 +246,7 @@ const SunoStudio = () => {
|
||||
type="text"
|
||||
value={style}
|
||||
onChange={(e) => setStyle(e.target.value)}
|
||||
placeholder="Ex: Pop, Rock, Cinematic, etc."
|
||||
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>
|
||||
@ -242,7 +267,7 @@ const SunoStudio = () => {
|
||||
<textarea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="Descreva a música que você quer criar... Ex: Um pop animado sobre o verão."
|
||||
placeholder="Descreva a música... Ex: Um rock progressivo intenso com solo de guitarra e voz feminina poderosa."
|
||||
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>
|
||||
@ -261,8 +286,8 @@ const SunoStudio = () => {
|
||||
<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</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</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -274,7 +299,7 @@ const SunoStudio = () => {
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<BaseIcon path={mdiRefresh} size={24} className="animate-spin" />
|
||||
Gerando...
|
||||
Gerando Áudio Real...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@ -317,6 +342,14 @@ const SunoStudio = () => {
|
||||
<BaseIcon path={currentTrack?.id === track.id && isPlaying ? mdiPause : mdiPlay} size={32} />
|
||||
</div>
|
||||
</button>
|
||||
<div className="absolute top-2 right-2 flex gap-2 opacity-0 group-hover:opacity-100 transition-all">
|
||||
<button onClick={() => openLyrics(track)} className="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center backdrop-blur-md">
|
||||
<BaseIcon path={mdiTextBoxOutline} size={16} />
|
||||
</button>
|
||||
<button onClick={() => handleDownload(track)} className="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center backdrop-blur-md">
|
||||
<BaseIcon path={mdiDownload} size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-start">
|
||||
@ -363,12 +396,20 @@ const SunoStudio = () => {
|
||||
{currentTrack && (
|
||||
<div className="h-[120px] bg-[#0A0A0A]/95 border-t border-white/5 flex items-center px-8 z-[200]">
|
||||
<div className="flex items-center w-[300px] gap-4">
|
||||
<div className="w-16 h-16 rounded-xl bg-gradient-to-br from-[#00E5FF] to-purple-600 flex items-center justify-center flex-shrink-0">
|
||||
<div className="w-16 h-16 rounded-xl bg-gradient-to-br from-[#00E5FF] to-purple-600 flex items-center justify-center flex-shrink-0 relative group">
|
||||
<BaseIcon path={mdiMusic} size={32} className="text-white" />
|
||||
<button
|
||||
onClick={() => openLyrics(currentTrack)}
|
||||
className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-all flex items-center justify-center rounded-xl"
|
||||
>
|
||||
<BaseIcon path={mdiTextBoxOutline} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h4 className="text-white font-black text-lg uppercase italic truncate">{currentTrack?.title}</h4>
|
||||
<p className="text-[10px] font-bold text-gray-500 uppercase truncate">AI Generated Artist</p>
|
||||
<p className="text-[10px] font-bold text-gray-500 uppercase truncate">
|
||||
{currentTrack?.ai_data?.mood || 'AI Professional Production'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -398,6 +439,14 @@ const SunoStudio = () => {
|
||||
</div>
|
||||
|
||||
<div className="w-[300px] flex justify-end items-center gap-4">
|
||||
<button
|
||||
onClick={() => handleDownload(currentTrack)}
|
||||
className="p-2 hover:bg-white/5 rounded-full text-gray-500 hover:text-[#00E5FF] transition-all flex items-center gap-2"
|
||||
title="Baixar MP3"
|
||||
>
|
||||
<BaseIcon path={mdiDownload} size={20} />
|
||||
<span className="text-[10px] font-black uppercase hidden lg:block">Baixar</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<BaseIcon path={volume === 0 ? mdiVolumeMute : volume < 0.5 ? mdiVolumeMedium : mdiVolumeHigh} size={20} className="text-gray-500" />
|
||||
<input
|
||||
@ -422,6 +471,62 @@ const SunoStudio = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lyrics Modal */}
|
||||
{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)} />
|
||||
<div className="relative bg-[#0D0D0D] border border-white/10 rounded-[40px] w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col shadow-2xl">
|
||||
<header className="p-8 border-b border-white/5 flex justify-between items-center">
|
||||
<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}
|
||||
</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">
|
||||
<BaseIcon path={mdiClose} size={24} />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-8 aside-scrollbars space-y-8">
|
||||
{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>
|
||||
<p className="text-lg font-medium leading-relaxed whitespace-pre-wrap">{text}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<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"
|
||||
>
|
||||
Tocar agora
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<style jsx global>{`
|
||||
.aside-scrollbars::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user