// 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 = `
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);