// AI HiFi Frontend - Secure Client-Side Code document.addEventListener('DOMContentLoaded', function() { const pairingButton = document.querySelector('.pairing-button'); const resultsContainer = document.getElementById('pairing-results'); const speakerInput = document.getElementById('speaker-input'); const amplifierInput = document.getElementById('amplifier-input'); const soundPreference = document.getElementById('sound-preference'); const loadingIndicator = document.createElement('div'); loadingIndicator.className = 'loading-indicator'; loadingIndicator.innerHTML = `

Analyzing pairing using AI and forum data...

`; async function fetchPairingAnalysis(speaker, amplifier, preferences) { try { const apiUrl = window.AI_HIFI_CONFIG?.API_BASE_URL || ''; const response = await fetch(`${apiUrl}/api/pairing`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ speaker: speaker.trim(), amplifier: amplifier.trim(), preferences: { soundSignature: preferences.soundPreference, roomSize: preferences.roomSize, budget: preferences.budget, primaryGenres: preferences.genres } }) }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); if (response.status === 429) { throw new Error('Too many requests. Please wait a moment and try again.'); } else if (response.status === 400) { throw new Error(errorData.error || 'Invalid input. Please check your speaker and amplifier names.'); } else if (response.status === 503) { throw new Error('AI analysis service is temporarily unavailable. Please try again later.'); } else { throw new Error(errorData.error || 'Analysis failed. Please try again.'); } } return await response.json(); } catch (error) { console.error('API Error:', error); throw error; } } function displayPairingResults(pairingData) { const { speaker, amplifier, analysis } = pairingData; resultsContainer.innerHTML = `

${speaker.productName} + ${amplifier.productName}

${analysis.compatibilityScore}/100 ${analysis.overallAssessment}

Technical Compatibility

Impedance: ${analysis.technicalMatch.impedanceMatch}

Power: ${analysis.technicalMatch.powerMatch}

Sensitivity: ${analysis.technicalMatch.sensitivityMatch}

Expected Sound Quality

Signature: ${analysis.soundQuality.expectedSignature}

Strengths:
    ${analysis.soundQuality.strengths.map(strength => `
  • ${strength}
  • `).join('')}
${analysis.soundQuality.potentialWeaknesses.length > 0 ? `
Potential Limitations:
    ${analysis.soundQuality.potentialWeaknesses.map(weakness => `
  • ${weakness}
  • `).join('')}
` : ''}
Recommended Genres:

${analysis.soundQuality.genreRecommendations.join(', ')}

Setup Recommendations

Room Size: ${analysis.practicalConsiderations.roomSize}

Placement: ${analysis.practicalConsiderations.placement}

${analysis.practicalConsiderations.additionalEquipment.length > 0 ? `
Consider Adding:
    ${analysis.practicalConsiderations.additionalEquipment.map(item => `
  • ${item}
  • `).join('')}
` : ''}
${analysis.alternativeRecommendations.length > 0 ? `

Alternative Recommendations

${analysis.alternativeRecommendations.map(alt => `
${alt.product}

${alt.reason}

Trade-offs: ${alt.tradeoffs}
`).join('')}
` : ''}
Analysis based on ${speaker.sourceCount + amplifier.sourceCount} forum discussions and reviews Last updated: ${new Date(speaker.lastUpdated).toLocaleDateString()}
`; resultsContainer.classList.add('active'); } function displayError(error) { let errorTitle = 'Analysis Failed'; let errorMessage = error.message; let suggestion = 'Please try again in a few moments.'; if (error.message.includes('Too many requests')) { errorTitle = 'Rate Limited'; suggestion = 'Please wait a moment before making another request.'; } else if (error.message.includes('temporarily unavailable')) { errorTitle = 'Service Unavailable'; suggestion = 'Our AI analysis service is temporarily down. Please try again later.'; } else if (error.message.includes('Invalid input')) { errorTitle = 'Invalid Input'; suggestion = 'Please check that you\'ve entered valid speaker and amplifier model names.'; } resultsContainer.innerHTML = `

${errorTitle}

${errorMessage}

${suggestion}

`; resultsContainer.classList.add('active'); } function showLoading() { resultsContainer.innerHTML = ''; resultsContainer.appendChild(loadingIndicator); resultsContainer.classList.add('active'); } async function handlePairingRequest() { const speaker = speakerInput.value.trim(); const amplifier = amplifierInput.value.trim(); const preferences = { soundPreference: soundPreference.value, roomSize: document.getElementById('room-size')?.value || 'medium', budget: document.getElementById('budget')?.value || 'mid-range', genres: Array.from(document.querySelectorAll('input[name="genres"]:checked')).map(cb => cb.value) }; if (!speaker || !amplifier) { displayError(new Error('Please enter both speaker and amplifier models')); return; } if (speaker.length > 100 || amplifier.length > 100) { displayError(new Error('Product names must be less than 100 characters')); return; } showLoading(); try { const pairingData = await fetchPairingAnalysis(speaker, amplifier, preferences); displayPairingResults(pairingData); } catch (error) { displayError(error); } } // Event Listeners pairingButton.addEventListener('click', handlePairingRequest); // Allow Enter key to trigger analysis [speakerInput, amplifierInput].forEach(input => { if (input) { input.addEventListener('keypress', function(e) { if (e.key === 'Enter') { handlePairingRequest(); } }); } }); // Recent pairings functionality async function loadRecentPairings() { try { const apiUrl = window.AI_HIFI_CONFIG?.API_BASE_URL || ''; const response = await fetch(`${apiUrl}/api/recent?limit=5`); const data = await response.json(); const recentContainer = document.getElementById('recent-pairings'); if (recentContainer && data.pairings.length > 0) { recentContainer.innerHTML = `

Recent Analyses

${data.pairings.map(pairing => `
${pairing.speaker} + ${pairing.amplifier} ${pairing.score}/100
`).join('')}
`; } } catch (error) { console.error('Failed to load recent pairings:', error); } } // Global function for recent pairing clicks window.loadPairing = function(speaker, amplifier) { if (speakerInput) speakerInput.value = speaker; if (amplifierInput) amplifierInput.value = amplifier; handlePairingRequest(); }; // Load recent pairings on page load loadRecentPairings(); // Auto-suggest functionality (simple implementation) function setupAutoSuggest(input, type) { if (!input) return; const suggestions = document.createElement('div'); suggestions.className = 'suggestions'; input.parentNode.appendChild(suggestions); input.addEventListener('input', async function() { const query = this.value.trim(); if (query.length < 3) { suggestions.style.display = 'none'; return; } // In a real implementation, you might want to fetch suggestions from the API // For now, we'll just show the input suggestions.innerHTML = `
${query}
`; suggestions.style.display = 'block'; }); input.addEventListener('blur', function() { setTimeout(() => { suggestions.style.display = 'none'; }, 200); }); } // Setup auto-suggest for inputs setupAutoSuggest(speakerInput, 'speaker'); setupAutoSuggest(amplifierInput, 'amplifier'); }); // CSS for new elements (to be added to styles.css) const aiPairingStyles = ` .loading-indicator { text-align: center; padding: 2rem; } .spinner { border: 4px solid #f3f3f3; border-top: 4px solid #2c5530; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 1rem; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .pairing-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 2px solid #2c5530; } .compatibility-score { text-align: right; } .compatibility-score .score { font-size: 2rem; font-weight: bold; color: #2c5530; display: block; } .compatibility-score .assessment { font-size: 0.9rem; color: #666; } .analysis-sections { display: grid; grid-template-columns: 1fr; gap: 1.5rem; } .analysis-sections > div { background: #f9f9f9; padding: 1rem; border-radius: 8px; border-left: 4px solid #2c5530; } .analysis-sections h4 { margin-top: 0; color: #2c5530; } .analysis-sections h5 { margin: 1rem 0 0.5rem; color: #444; } .analysis-sections ul { margin: 0.5rem 0; padding-left: 1.5rem; } .alternative-item { margin-bottom: 1rem; padding: 0.5rem; background: white; border-radius: 4px; } .error-message { background: #fee; border: 1px solid #fcc; color: #c00; padding: 1rem; border-radius: 8px; text-align: center; } .data-sources { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #ddd; } .data-sources small { display: block; color: #666; margin: 0.25rem 0; } .recent-list { max-height: 200px; overflow-y: auto; } .recent-item { display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; margin: 0.25rem 0; background: #f5f5f5; border-radius: 4px; cursor: pointer; transition: background 0.2s; } .recent-item:hover { background: #e5e5e5; } .recent-item .score { font-weight: bold; color: #2c5530; } .suggestions { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ddd; border-top: none; border-radius: 0 0 4px 4px; z-index: 1000; display: none; } .suggestion-item { padding: 0.5rem; cursor: pointer; border-bottom: 1px solid #eee; } .suggestion-item:hover { background: #f5f5f5; } .suggestion-item:last-child { border-bottom: none; } @media (min-width: 768px) { .analysis-sections { grid-template-columns: 1fr 1fr; } .technical-match { grid-column: 1 / -1; } } `; // Inject styles const styleSheet = document.createElement('style'); styleSheet.textContent = aiPairingStyles; document.head.appendChild(styleSheet);