1/** 2 * AI Song Lyrics Generator - Main JavaScript 3 * Handles lyrics generation, API calls, and result display 4 */ 5 6// Global configuration 7const DOMAIN_CONFIG = { 8 API_BASE: 'https://api.lyricsgenerator.blog', 9 FRONTEND_BASE: window.location.origin 10}; 11 12const API_ENDPOINTS = { 13 BASE: `${DOMAIN_CONFIG.API_BASE}/api`, 14 LYRICS: { 15 GENERATE: `${DOMAIN_CONFIG.API_BASE}/api/lyrics/generate`, 16 QUOTA: `${DOMAIN_CONFIG.API_BASE}/api/lyrics/quota`, 17 HEALTH: `${DOMAIN_CONFIG.API_BASE}/api/lyrics/health` 18 } 19}; 20 21window.DOMAIN_CONFIG = DOMAIN_CONFIG; 22window.API_ENDPOINTS = API_ENDPOINTS; 23 24// Language configuration 25const SUPPORTED_LANGUAGES = ['en', 'es', 'ja', 'fr', 'de', 'pt', 'ko', 'it']; 26const DEFAULT_LANGUAGE = 'en'; 27 28function getCurrentLanguage() { 29 const docLang = document.documentElement.lang; 30 if (docLang && SUPPORTED_LANGUAGES.includes(docLang)) { 31 return docLang; 32 } 33 return DEFAULT_LANGUAGE; 34} 35 36window.getCurrentLanguage = getCurrentLanguage; 37 38/** 39 * AI Lyrics Generator Class 40 */ 41class LyricsGenerator { 42 constructor() { 43 this.lyricsInput = document.getElementById('lyricsInput'); 44 this.styleSelect = document.getElementById('styleSelect'); 45 this.moodSelect = document.getElementById('moodSelect'); 46 this.structureSelect = document.getElementById('structureSelect'); 47 this.generateBtn = document.getElementById('generateBtn'); 48 this.trySampleBtn = document.getElementById('trySampleBtn'); 49 50 this.previewDefault = document.getElementById('previewDefault'); 51 this.previewLoading = document.getElementById('previewLoading'); 52 this.previewResult = document.getElementById('previewResult'); 53 this.generatedLyrics = document.getElementById('generatedLyrics'); 54 this.resultTitle = document.getElementById('resultTitle'); 55 56 this.copyBtn = document.getElementById('copyBtn'); 57 this.downloadBtn = document.getElementById('downloadBtn'); 58 this.shareBtn = document.getElementById('shareBtn'); 59 this.regenerateBtn = document.getElementById('regenerateBtn'); 60 61 this.lang = getCurrentLanguage(); 62 this.lastRequest = null; 63 64 // Get translations for current language 65 this.translations = window.FRONTEND_TRANSLATIONS?.[this.lang] || window.FRONTEND_TRANSLATIONS?.en || {}; 66 67 // Get sample ideas from translations 68 this.sampleIdeas = window.PAGE_SAMPLE_IDEAS || this.translations.sampleIdeas || []; 69 70 this.init(); 71 } 72 73 init() { 74 if (!this.generateBtn) return; 75 76 this.generateBtn.addEventListener('click', () => this.handleGenerate()); 77 78 if (this.trySampleBtn) { 79 this.trySampleBtn.addEventListener('click', () => this.insertSample()); 80 } 81 82 if (this.regenerateBtn) { 83 this.regenerateBtn.addEventListener('click', () => this.handleGenerate()); 84 } 85 86 if (this.copyBtn) { 87 this.copyBtn.addEventListener('click', () => this.copyLyrics()); 88 } 89 90 if (this.downloadBtn) { 91 this.downloadBtn.addEventListener('click', () => this.downloadLyrics()); 92 } 93 94 if (this.shareBtn) { 95 this.shareBtn.addEventListener('click', () => this.shareLyrics()); 96 } 97 98 // Enter key to generate 99 if (this.lyricsInput) { 100 this.lyricsInput.addEventListener('keydown', (e) => { 101 if (e.key === 'Enter' && e.ctrlKey) { 102 this.handleGenerate(); 103 } 104 }); 105 } 106 } 107 108 insertSample() { 109 if (!this.lyricsInput) return; 110 const randomIndex = Math.floor(Math.random() * this.sampleIdeas.length); 111 this.lyricsInput.value = this.sampleIdeas[randomIndex]; 112 this.lyricsInput.focus(); 113 } 114 115 async handleGenerate() { 116 const idea = this.lyricsInput?.value?.trim(); 117 const t = this.translations.notifications || {}; 118 119 if (!idea || idea.length < 3) { 120 this.showNotification(t.errorMinLength || 'Please enter a song idea (at least 3 characters)', 'error'); 121 return; 122 } 123 124 const mode = window.LYRICS_GENERATOR_MODE || 'song'; 125 const style = this.styleSelect?.value || 'auto'; 126 const mood = this.moodSelect?.value || 'auto'; 127 const structure = this.structureSelect?.value || null; 128 129 this.lastRequest = { idea, style, mood, mode, structure }; 130 this.showLoading(); 131 132 try { 133 const response = await fetch(API_ENDPOINTS.LYRICS.GENERATE, { 134 method: 'POST', 135 headers: { 136 'Content-Type': 'application/json', 137 }, 138 body: JSON.stringify({ 139 idea: idea, 140 style: style, 141 mood: mood, 142 language: this.lang, 143 mode: mode, 144 rapSubtype: mode === 'rap' ? style : undefined, 145 structure: structure 146 }) 147 }); 148 149 const result = await response.json(); 150 151 if (!result.success) { 152 if (result.error === 'RATE_LIMIT_EXCEEDED') { 153 this.showNotification(t.rateLimitExceeded || 'Daily limit exceeded. Please try again tomorrow.', 'error'); 154 this.showDefault(); 155 return; 156 } 157 throw new Error(t.errorGeneration || result.message || 'Generation failed'); 158 } 159 160 this.displayResult(result.data); 161 162 } catch (error) { 163 console.error('Generation error:', error); 164 this.showNotification(error.message || t.errorGeneration || 'Failed to generate lyrics. Please try again.', 'error'); 165 this.showDefault(); 166 } 167 } 168 169 showLoading() { 170 if (this.previewDefault) this.previewDefault.style.display = 'none'; 171 if (this.previewResult) this.previewResult.style.display = 'none'; 172 if (this.previewLoading) this.previewLoading.style.display = 'flex'; 173 174 this.animateLoadingSteps(); 175 } 176 177 animateLoadingSteps() { 178 const steps = [ 179 document.getElementById('loadStep1'), 180 document.getElementById('loadStep2'), 181 document.getElementById('loadStep3') 182 ]; 183 184 steps.forEach((step, index) => { 185 if (step) { 186 step.classList.remove('active', 'step-complete'); 187 step.querySelector('i').className = 'fas fa-circle'; 188 } 189 }); 190 191 let currentStep = 0; 192 const interval = setInterval(() => { 193 if (currentStep < steps.length && steps[currentStep]) { 194 if (currentStep > 0 && steps[currentStep - 1]) { 195 steps[currentStep - 1].classList.remove('active'); 196 steps[currentStep - 1].classList.add('step-complete'); 197 steps[currentStep - 1].querySelector('i').className = 'fas fa-check-circle'; 198 } 199 steps[currentStep].classList.add('active'); 200 steps[currentStep].querySelector('i').className = 'fas fa-circle-notch fa-spin'; 201 currentStep++; 202 } else { 203 clearInterval(interval); 204 } 205 }, 2000); 206 207 this.loadingInterval = interval; 208 } 209 210 showDefault() { 211 if (this.loadingInterval) clearInterval(this.loadingInterval); 212 if (this.previewLoading) this.previewLoading.style.display = 'none'; 213 if (this.previewResult) this.previewResult.style.display = 'none'; 214 if (this.previewDefault) this.previewDefault.style.display = 'flex'; 215 } 216 217 displayResult(data) { 218 if (this.loadingInterval) clearInterval(this.loadingInterval); 219 220 if (this.previewLoading) this.previewLoading.style.display = 'none'; 221 if (this.previewDefault) this.previewDefault.style.display = 'none'; 222 if (this.previewResult) this.previewResult.style.display = 'flex'; 223 224 // Set title 225 if (this.resultTitle) { 226 this.resultTitle.textContent = data.title || 'Your Generated Song'; 227 } 228 229 // Display lyrics 230 if (this.generatedLyrics && data.sections) { 231 let html = ''; 232 for (const section of data.sections) { 233 html += `<p class="section-label">${this.escapeHtml(section.label)}</p>`; 234 for (const line of section.lines) { 235 html += `<p class="lyrics-line">${this.escapeHtml(line)}</p>`; 236 } 237 } 238 this.generatedLyrics.innerHTML = html; 239 } else if (this.generatedLyrics && data.lyrics) { 240 // Fallback to plain lyrics 241 const lines = data.lyrics.split('\n'); 242 let html = ''; 243 for (const line of lines) { 244 if (line.startsWith('[') && line.endsWith(']')) { 245 html += `<p class="section-label">${this.escapeHtml(line.slice(1, -1))}</p>`; 246 } else if (line.trim()) { 247 html += `<p class="lyrics-line">${this.escapeHtml(line)}</p>`; 248 } 249 } 250 this.generatedLyrics.innerHTML = html; 251 } 252 253 // Store for copy/download 254 this.currentLyrics = data; 255 } 256 257 escapeHtml(text) { 258 const div = document.createElement('div'); 259 div.textContent = text; 260 return div.innerHTML; 261 } 262 263 copyLyrics() { 264 if (!this.currentLyrics) return; 265 const t = this.translations.notifications || {}; 266 267 let text = `${this.currentLyrics.title || 'Generated Song'}\n\n`; 268 269 if (this.currentLyrics.sections) { 270 for (const section of this.currentLyrics.sections) { 271 text += `[${section.label}
271]\n`; 272 text += section.lines.join('\n') + '\n\n'; 273 } 274 } else if (this.currentLyrics.lyrics) { 275 text += this.currentLyrics.lyrics; 276 } 277 278 navigator.clipboard.writeText(text.trim()).then(() => { 279 this.showNotification(t.copySuccess || 'Lyrics copied to clipboard!', 'success'); 280 }).catch(() => { 281 this.showNotification(t.copyError || 'Failed to copy lyrics', 'error'); 282 }); 283 } 284 285 downloadLyrics() { 286 if (!this.currentLyrics) return; 287 const t = this.translations.notifications || {}; 288 289 let text = `${this.currentLyrics.title || 'Generated Song'}\n`; 290 text += `Style: ${this.currentLyrics.style || 'N/A'}\n`; 291 text += `Mood: ${this.currentLyrics.mood || 'N/A'}\n\n`; 292 293 if (this.currentLyrics.sections) { 294 for (const section of this.currentLyrics.sections) { 295 text += `[${section.label}]\n`; 296 text += section.lines.join('\n') + '\n\n'; 297 } 298 } else if (this.currentLyrics.lyrics) { 299 text += this.currentLyrics.lyrics; 300 } 301 302 const blob = new Blob([text.trim()], { type: 'text/plain' }); 303 const url = URL.createObjectURL(blob); 304 const a = document.createElement('a'); 305 a.href = url; 306 a.download = `${(this.currentLyrics.title || 'lyrics').replace(/[^a-z0-9]/gi, '_')}.txt`; 307 a.click(); 308 URL.revokeObjectURL(url); 309 310 this.showNotification(t.downloadSuccess || 'Lyrics downloaded!', 'success'); 311 } 312 313 shareLyrics() { 314 const t = this.translations.share || {}; 315 if (navigator.share) { 316 navigator.share({ 317 title: this.currentLyrics?.title || t.title || 'AI Generated Lyrics', 318 text: t.text || 'Check out these AI generated song lyrics!', 319 url: window.location.href 320 }).catch(() => {}); 321 } else { 322 this.copyLyrics(); 323 } 324 } 325 326 showNotification(message, type = 'info') { 327 const alertClass = type === 'error' ? 'alert-danger' : type === 'success' ? 'alert-success' : 'alert-info'; 328 const notification = document.createElement('div'); 329 notification.className = `alert ${alertClass}`; 330 notification.textContent = message; 331 notification.style.cssText = ` 332 position: fixed; top: 20px; right: 20px; z-index: 9999; 333 min-width: 280px; padding: 12px 20px; border-radius: 8px; 334 animation: slideIn 0.3s ease-out; 335 `; 336 337 document.body.appendChild(notification); 338 339 setTimeout(() => { 340 notification.style.animation = 'slideOut 0.3s ease-out'; 341 setTimeout(() => notification.remove(), 300); 342 }, 3000); 343 } 344} 345 346// Notification animations 347const style = document.createElement('style'); 348style.textContent = ` 349 @keyframes slideIn { 350 from { transform: translateX(400px); opacity: 0; } 351 to { transform: translateX(0); opacity: 1; } 352 } 353 @keyframes slideOut { 354 from { transform: translateX(0); opacity: 1; } 355 to { transform: translateX(400px); opacity: 0; } 356 } 357`; 358document.head.appendChild(style); 359 360// Initialize on DOM ready 361document.addEventListener('DOMContentLoaded', function() { 362 // Initialize lyrics generator 363 new LyricsGenerator(); 364 365 // Smooth scrolling for anchor links 366 const navLinks = document.querySelectorAll('a[href^="#"]'); 367 navLinks.forEach(link => { 368 link.addEventListener('click', function(e) { 369 const href = this.getAttribute('href'); 370 if (href === '#' || href === '') return; 371 372 e.preventDefault(); 373 const targetId = href.substring(1); 374 const targetElement = document.getElementById(targetId); 375 376 if (targetElement) { 377 const navbar = document.querySelector('.navbar'); 378 const navbarHeight = navbar ? navbar.offsetHeight : 0; 379 const targetPosition = targetElement.offsetTop - navbarHeight - 20; 380 381 window.scrollTo({ 382 top: targetPosition, 383 behavior: 'smooth' 384 }); 385 } 386 }); 387 }); 388});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.