/** * Error Display Utility * * Parses and displays user-friendly error messages from the backend. * Handles both JSON-formatted errors (ERROR_JSON:...) and plain text errors. */ /** * Parse a stored error message from the backend. * @param {string} text - The stored transcription/error text * @returns {Object|null} - Parsed error object or null if not an error */ export function parseStoredError(text) { if (!text) return null; // Check for JSON-formatted error if (text.startsWith('ERROR_JSON:')) { try { const jsonStr = text.substring(11); // Remove 'ERROR_JSON:' prefix const data = JSON.parse(jsonStr); return { title: data.t || 'Error', message: data.m || 'An error occurred', guidance: data.g || '', icon: data.i || 'fa-exclamation-circle', type: data.y || 'unknown', isKnown: data.k || false, technical: data.d || '', isFormattedError: true }; } catch (e) { console.error('Failed to parse error JSON:', e); } } // Check for legacy error format (starts with common error prefixes) const errorPrefixes = [ 'Transcription failed:', 'Processing failed:', 'ASR processing failed:', 'Audio extraction failed:', 'Error:' ]; for (const prefix of errorPrefixes) { if (text.startsWith(prefix)) { // Parse the error using pattern matching return parseUnformattedError(text); } } return null; } /** * Parse an unformatted error message and try to make it user-friendly. * @param {string} text - The raw error text * @returns {Object} - Parsed error object */ function parseUnformattedError(text) { const lowerText = text.toLowerCase(); // Known error patterns const patterns = [ { patterns: ['maximum content size limit', 'file too large', '413', 'payload too large', 'exceeded'], title: 'File Too Large', message: 'The audio file exceeds the maximum size allowed by the transcription service.', guidance: 'Try enabling audio chunking in your settings, or compress the audio file before uploading.', icon: 'fa-file-audio', type: 'size_limit' }, { patterns: ['timed out', 'timeout', 'deadline exceeded'], title: 'Processing Timeout', message: 'The transcription took too long to complete.', guidance: 'This can happen with very long recordings. Try splitting the audio into smaller parts.', icon: 'fa-clock', type: 'timeout' }, { patterns: ['401', 'unauthorized', 'invalid api key', 'authentication failed', 'incorrect api key'], title: 'Authentication Error', message: 'The transcription service rejected the API credentials.', guidance: 'Please check that the API key is correct and has not expired.', icon: 'fa-key', type: 'auth' }, { patterns: ['rate limit', 'too many requests', '429', 'quota exceeded'], title: 'Rate Limit Exceeded', message: 'Too many requests were sent to the transcription service.', guidance: 'Please wait a few minutes and try reprocessing.', icon: 'fa-hourglass-half', type: 'rate_limit' }, { patterns: ['connection refused', 'connection reset', 'could not connect', 'network unreachable'], title: 'Connection Error', message: 'Could not connect to the transcription service.', guidance: 'Check your internet connection and ensure the service is available.', icon: 'fa-wifi', type: 'connection' }, { patterns: ['503', '502', '500', 'service unavailable', 'server error', 'internal server error'], title: 'Service Unavailable', message: 'The transcription service is temporarily unavailable.', guidance: 'This is usually temporary. Please try again in a few minutes.', icon: 'fa-server', type: 'service_error' }, { patterns: ['invalid file format', 'unsupported format', 'could not decode', 'corrupt', 'not valid audio'], title: 'Invalid Audio Format', message: 'The audio file format is not supported or the file may be corrupted.', guidance: 'Try converting the audio to MP3 or WAV format before uploading.', icon: 'fa-file-audio', type: 'format' }, { patterns: ['audio extraction failed', 'ffmpeg failed', 'no audio stream'], title: 'Audio Extraction Failed', message: 'Could not extract audio from the uploaded file.', guidance: 'Try converting the file to a standard audio format (MP3, WAV) before uploading.', icon: 'fa-file-video', type: 'extraction' } ]; // Check patterns for (const pattern of patterns) { for (const p of pattern.patterns) { if (lowerText.includes(p)) { return { title: pattern.title, message: pattern.message, guidance: pattern.guidance, icon: pattern.icon, type: pattern.type, isKnown: true, technical: text, isFormattedError: true }; } } } // Unknown error - clean it up as best we can let cleanMessage = text; for (const prefix of ['Transcription failed:', 'Processing failed:', 'Error:', 'ASR processing failed:']) { if (cleanMessage.startsWith(prefix)) { cleanMessage = cleanMessage.substring(prefix.length).trim(); } } // Truncate if too long if (cleanMessage.length > 200) { cleanMessage = cleanMessage.substring(0, 200) + '...'; } return { title: 'Processing Error', message: cleanMessage, guidance: 'If this error persists, try reprocessing the recording.', icon: 'fa-exclamation-circle', type: 'unknown', isKnown: false, technical: text, isFormattedError: true }; } /** * Check if a transcription text is actually an error message. * @param {string} text - The transcription text * @returns {boolean} */ export function isErrorMessage(text) { if (!text) return false; if (text.startsWith('ERROR_JSON:')) return true; const errorPrefixes = [ 'Transcription failed:', 'Processing failed:', 'ASR processing failed:', 'Audio extraction failed:' ]; return errorPrefixes.some(prefix => text.startsWith(prefix)); } /** * Generate HTML for displaying an error nicely. * @param {Object} error - Parsed error object from parseStoredError * @param {boolean} showTechnical - Whether to show technical details * @returns {string} - HTML string */ export function generateErrorHTML(error, showTechnical = false) { if (!error) return ''; const typeColors = { size_limit: 'amber', timeout: 'orange', auth: 'red', rate_limit: 'yellow', connection: 'blue', service_error: 'purple', format: 'pink', extraction: 'indigo', billing: 'red', model: 'gray', unknown: 'gray' }; const color = typeColors[error.type] || 'gray'; let html = `
${escapeHtml(error.message)}
${error.guidance ? `${escapeHtml(error.technical)}