Changeset 3437893
- Timestamp:
- 01/12/2026 03:45:01 PM (3 months ago)
- Location:
- post2podcast
- Files:
-
- 12 edited
- 2 copied
-
tags/1.2.6/trunk (copied) (copied from post2podcast/trunk)
-
tags/1.2.6/trunk/assets/js/admin.js (modified) (14 diffs)
-
tags/1.2.6/trunk/includes/class-post2podcast-api.php (modified) (3 diffs)
-
tags/1.2.6/trunk/post2podcast.php (modified) (2 diffs)
-
tags/1.2.6/trunk/readme.txt (modified) (2 diffs)
-
tags/1.2.7 (copied) (copied from post2podcast/trunk)
-
tags/1.2.7/assets/js/admin.js (modified) (14 diffs)
-
tags/1.2.7/includes/class-post2podcast-api.php (modified) (3 diffs)
-
tags/1.2.7/post2podcast.php (modified) (2 diffs)
-
tags/1.2.7/readme.txt (modified) (2 diffs)
-
trunk/assets/js/admin.js (modified) (14 diffs)
-
trunk/includes/class-post2podcast-api.php (modified) (3 diffs)
-
trunk/post2podcast.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
post2podcast/tags/1.2.6/trunk/assets/js/admin.js
r3331992 r3437893 14 14 var generationInProgress = false; 15 15 var progressInterval = null; // For simulated progress 16 var lastMessageTime = null; // Track when we last received a message 17 var timeoutCheckInterval = null; // For checking message timeout 16 18 17 19 // Handle delete podcast button click (for existing audio) … … 39 41 eventSource = null; 40 42 } 41 43 42 44 if (progressInterval) { 43 45 clearInterval(progressInterval); 44 46 progressInterval = null; 45 47 } 46 48 49 if (timeoutCheckInterval) { 50 clearInterval(timeoutCheckInterval); 51 timeoutCheckInterval = null; 52 } 53 47 54 generationInProgress = false; 48 55 … … 144 151 if (response.success) { 145 152 var detection = response.data.detection_result; 146 153 147 154 if (detection.language_code) { 148 155 // Update the UI with detected language 149 var languageInfo = '<p><strong>Detected Language:</strong> ' + 150 (detection.language_name || detection.language_code) + 156 var languageInfo = '<p><strong>Detected Language:</strong> ' + 157 (detection.language_name || detection.language_code) + 151 158 ' <span class="language-code">(' + detection.language_code + ')</span></p>'; 152 languageInfo += '<p class="description">Voices will be automatically selected based on detected language.</p>'; 153 159 154 160 $('#detected-language-info').html(languageInfo); 155 156 // Showsuggested voices if available161 162 // Automatically select suggested voices if available 157 163 if (detection.suggested_voices) { 158 164 console.log('Suggested voices for ' + detection.language_code + ':', detection.suggested_voices); 165 166 var voice1 = detection.suggested_voices.voice1; 167 var voice2 = detection.suggested_voices.voice2; 168 169 // Set the voice selectors to the suggested voices 170 var $voice1Select = $('#post2podcast_voice1'); 171 var $voice2Select = $('#post2podcast_voice2'); 172 173 // Check if the voices exist in the dropdown and select them 174 if ($voice1Select.find('option[value="' + voice1 + '"]').length > 0) { 175 $voice1Select.val(voice1); 176 console.log('Selected voice1: ' + voice1); 177 } else { 178 console.warn('Voice ' + voice1 + ' not found in dropdown'); 179 } 180 181 if ($voice2Select.find('option[value="' + voice2 + '"]').length > 0) { 182 $voice2Select.val(voice2); 183 console.log('Selected voice2: ' + voice2); 184 } else { 185 console.warn('Voice ' + voice2 + ' not found in dropdown'); 186 } 187 188 // Update the language info to show selected voices 189 languageInfo += '<p class="description" style="color: #28a745; font-weight: 500;">✓ Voices automatically selected: ' + 190 voice1 + ' and ' + voice2 + '</p>'; 191 $('#detected-language-info').html(languageInfo); 192 } else { 193 languageInfo += '<p class="description">Please select appropriate voices for this language.</p>'; 194 $('#detected-language-info').html(languageInfo); 159 195 } 160 196 } else { … … 230 266 eventSource.close(); 231 267 } 232 233 // Create a timestamp for this generation session 234 var sessionId = 'session-' + Date.now(); 235 268 269 // Clear any existing timeout check 270 if (timeoutCheckInterval) { 271 clearInterval(timeoutCheckInterval); 272 } 273 236 274 // Start Server-Sent Events connection 237 275 try { … … 256 294 257 295 eventSource = new EventSource(url); 258 296 259 297 eventSource.onopen = function() { 260 298 addStatusMessage('Connected to generation service', 'info'); 299 lastMessageTime = Date.now(); 300 301 // Start timeout monitoring - if no message for 3 minutes, assume something went wrong 302 timeoutCheckInterval = setInterval(function() { 303 var timeSinceLastMessage = Date.now() - lastMessageTime; 304 var maxIdleTime = 180000; // 3 minutes in milliseconds 305 306 if (timeSinceLastMessage > maxIdleTime && generationInProgress) { 307 addStatusMessage('❌ Server stopped responding. The generation may have failed.', 'error'); 308 if (progressInterval) clearInterval(progressInterval); 309 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 310 if (eventSource) { 311 eventSource.close(); 312 eventSource = null; 313 } 314 resetUI(false); 315 } 316 }, 10000); // Check every 10 seconds 261 317 }; 262 318 263 319 eventSource.onmessage = function(event) { 320 lastMessageTime = Date.now(); // Update last message time 264 321 try { 265 322 var data = JSON.parse(event.data); … … 272 329 $progressBar.css('width', '100%').text('100%'); 273 330 addStatusMessage('✓ ' + data.message, 'success'); 274 331 275 332 // Show the audio preview instead of reloading 276 333 if (data.audio_url) { … … 278 335 addStatusMessage('Audio preview is ready!', 'success'); 279 336 } 280 337 281 338 // Update credit display after successful generation 282 339 updateCreditDisplay(); 283 340 284 341 // Instead of full resetUI, specifically manage button visibility 285 342 generationInProgress = false; // Allow new actions … … 293 350 294 351 // Explicitly close the connection on success from client-side 352 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 295 353 if (eventSource) { 296 354 addStatusMessage('Closing SSE connection from client after success.', 'info'); … … 298 356 eventSource = null; // Prevent resetUI or onerror from trying to close it again 299 357 } 300 358 301 359 } else if (data.status === 'error') { 360 // Stop the simulated progress immediately on error 361 if (progressInterval) clearInterval(progressInterval); 362 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 363 // Do NOT set progress to 100% on error 302 364 addStatusMessage('❌ Error: ' + data.message, 'error'); 303 resetUI( true); // Pass true to indicate success, so progress bar stays at 100% briefly365 resetUI(false); // Pass false to indicate error - this will hide progress bar and reset UI 304 366 } 305 367 … … 314 376 eventSource.onerror = function(error) { 315 377 console.error('SSE Error:', error); 378 // Stop the simulated progress immediately 379 if (progressInterval) clearInterval(progressInterval); 380 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 381 316 382 if (eventSource.readyState === EventSource.CLOSED) { 317 addStatusMessage('Connection to server was closed', 'error'); 383 addStatusMessage('❌ Connection to server was closed. The server may have encountered an error.', 'error'); 384 } else if (eventSource.readyState === EventSource.CONNECTING) { 385 addStatusMessage('❌ Connection error while attempting to reconnect. Please try again.', 'error'); 318 386 } else { 319 addStatusMessage(' Connection error. Pleasetry again.', 'error');387 addStatusMessage('❌ Connection error. Please check your internet connection and try again.', 'error'); 320 388 } 321 if (progressInterval) clearInterval(progressInterval); 389 322 390 resetUI(false); // Pass false to indicate error/connection close 323 391 }; … … 366 434 } 367 435 368 function resetUI(isSuccess) { 436 function resetUI(isSuccess) { 369 437 generationInProgress = false; 370 438 $spinner.removeClass('is-active'); 371 if (progressInterval) clearInterval(progressInterval); 439 if (progressInterval) { 440 clearInterval(progressInterval); 441 progressInterval = null; 442 } 443 if (timeoutCheckInterval) { 444 clearInterval(timeoutCheckInterval); 445 timeoutCheckInterval = null; 446 } 372 447 373 448 if (!isSuccess) { // Only hide progress bar immediately if not a success case … … 377 452 // On success, the bar is at 100%. It will be hidden/reset if user navigates or starts new generation. 378 453 379 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 454 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 380 455 ($('.post2podcast-player audio source').length > 0 && $('.post2podcast-player audio source').attr('src') !== ''); 381 456 … … 389 464 $('#delete_podcast').hide(); 390 465 } 391 466 392 467 if (eventSource) { 393 468 eventSource.close(); … … 458 533 eventSource.close(); 459 534 } 535 if (timeoutCheckInterval) { 536 clearInterval(timeoutCheckInterval); 537 } 460 538 }); 461 539 -
post2podcast/tags/1.2.6/trunk/includes/class-post2podcast-api.php
r3437853 r3437893 328 328 } 329 329 if (!isset($job_response['audio_content'])) { 330 throw new Exception('Audio content not found in API response.'); 330 $error_detail = isset($job_response['detail']) ? $job_response['detail'] : 'Unknown error'; 331 throw new Exception('Audio generation failed on server: ' . $error_detail); 331 332 } 332 333 $audio_content = base64_decode($job_response['audio_content']); … … 682 683 $error_message = $response->get_error_message(); 683 684 error_log("Post2Podcast WP HTTP API Error (post2podcast_start_sync_generation): " . $error_message); 684 throw new Exception('API request failed (WP HTTP API error): ' . esc_html($error_message));685 return new WP_Error('connection_error', 'Connection error: ' . $error_message); 685 686 } 686 687 … … 690 691 if ($status_code >= 400) { 691 692 error_log("Post2Podcast API Error (post2podcast_start_sync_generation): Status " . $status_code . " - Response: " . $response_body); 692 throw new Exception('API request failed with status code ' . esc_html($status_code) . ': ' . esc_html($response_body)); 693 } 694 return json_decode($response_body, true); 693 694 // Try to parse error details from response 695 $error_data = json_decode($response_body, true); 696 $error_message = isset($error_data['detail']) ? $error_data['detail'] : $response_body; 697 698 return new WP_Error('api_error', 'Server error (' . $status_code . '): ' . $error_message); 699 } 700 701 $decoded = json_decode($response_body, true); 702 703 // Check if the response indicates a server-side error even with 200 status 704 if (!$decoded || (isset($decoded['error']) && $decoded['error'])) { 705 $error_msg = isset($decoded['error']) ? $decoded['error'] : 'Invalid response from server'; 706 error_log("Post2Podcast API Error: " . $error_msg); 707 return new WP_Error('generation_failed', $error_msg); 708 } 709 710 return $decoded; 695 711 } 696 712 -
post2podcast/tags/1.2.6/trunk/post2podcast.php
r3437853 r3437893 4 4 * Plugin server URI: https://github.com/samukbg/post2podcast-server 5 5 * Description: Convert WordPress posts into podcast episodes with AI voices 6 * Version: 1.2. 66 * Version: 1.2.7 7 7 * Author: Samuel Bezerra 8 8 * Author URI: https://github.com/samukbg … … 20 20 21 21 // Define plugin constants 22 define('POST2PODCAST_VERSION', '1.2. 6');22 define('POST2PODCAST_VERSION', '1.2.7'); 23 23 define('POST2PODCAST_PLUGIN_DIR', plugin_dir_path(__FILE__)); 24 24 define('POST2PODCAST_PLUGIN_URL', plugin_dir_url(__FILE__)); -
post2podcast/tags/1.2.6/trunk/readme.txt
r3437853 r3437893 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 67 Stable tag: 1.2.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 274 274 == Changelog == 275 275 276 = 1.2.7 = 277 * FIXED: Error handling now properly detects and reports server failures instead of showing 100% progress 278 * FIXED: Language detection now automatically selects appropriate Kokoro TTS voices in dropdown menus 279 * IMPROVED: Automatic server fallback - when one TTS server fails, automatically tries the next available server 280 * IMPROVED: Added 3-minute timeout detection to catch when server stops responding during generation 281 * IMPROVED: Better error messages showing specific failure reasons (connection errors, server errors, timeouts) 282 * IMPROVED: Progress bar now correctly stops on errors instead of completing to 100% 283 * ENHANCED: Backend now uses Kokoro TTS voice codes for all language-specific voice recommendations 284 276 285 = 1.2.6 = 277 286 * IMPROVED: Minor bug fixes and performance improvements. -
post2podcast/tags/1.2.7/assets/js/admin.js
r3331992 r3437893 14 14 var generationInProgress = false; 15 15 var progressInterval = null; // For simulated progress 16 var lastMessageTime = null; // Track when we last received a message 17 var timeoutCheckInterval = null; // For checking message timeout 16 18 17 19 // Handle delete podcast button click (for existing audio) … … 39 41 eventSource = null; 40 42 } 41 43 42 44 if (progressInterval) { 43 45 clearInterval(progressInterval); 44 46 progressInterval = null; 45 47 } 46 48 49 if (timeoutCheckInterval) { 50 clearInterval(timeoutCheckInterval); 51 timeoutCheckInterval = null; 52 } 53 47 54 generationInProgress = false; 48 55 … … 144 151 if (response.success) { 145 152 var detection = response.data.detection_result; 146 153 147 154 if (detection.language_code) { 148 155 // Update the UI with detected language 149 var languageInfo = '<p><strong>Detected Language:</strong> ' + 150 (detection.language_name || detection.language_code) + 156 var languageInfo = '<p><strong>Detected Language:</strong> ' + 157 (detection.language_name || detection.language_code) + 151 158 ' <span class="language-code">(' + detection.language_code + ')</span></p>'; 152 languageInfo += '<p class="description">Voices will be automatically selected based on detected language.</p>'; 153 159 154 160 $('#detected-language-info').html(languageInfo); 155 156 // Showsuggested voices if available161 162 // Automatically select suggested voices if available 157 163 if (detection.suggested_voices) { 158 164 console.log('Suggested voices for ' + detection.language_code + ':', detection.suggested_voices); 165 166 var voice1 = detection.suggested_voices.voice1; 167 var voice2 = detection.suggested_voices.voice2; 168 169 // Set the voice selectors to the suggested voices 170 var $voice1Select = $('#post2podcast_voice1'); 171 var $voice2Select = $('#post2podcast_voice2'); 172 173 // Check if the voices exist in the dropdown and select them 174 if ($voice1Select.find('option[value="' + voice1 + '"]').length > 0) { 175 $voice1Select.val(voice1); 176 console.log('Selected voice1: ' + voice1); 177 } else { 178 console.warn('Voice ' + voice1 + ' not found in dropdown'); 179 } 180 181 if ($voice2Select.find('option[value="' + voice2 + '"]').length > 0) { 182 $voice2Select.val(voice2); 183 console.log('Selected voice2: ' + voice2); 184 } else { 185 console.warn('Voice ' + voice2 + ' not found in dropdown'); 186 } 187 188 // Update the language info to show selected voices 189 languageInfo += '<p class="description" style="color: #28a745; font-weight: 500;">✓ Voices automatically selected: ' + 190 voice1 + ' and ' + voice2 + '</p>'; 191 $('#detected-language-info').html(languageInfo); 192 } else { 193 languageInfo += '<p class="description">Please select appropriate voices for this language.</p>'; 194 $('#detected-language-info').html(languageInfo); 159 195 } 160 196 } else { … … 230 266 eventSource.close(); 231 267 } 232 233 // Create a timestamp for this generation session 234 var sessionId = 'session-' + Date.now(); 235 268 269 // Clear any existing timeout check 270 if (timeoutCheckInterval) { 271 clearInterval(timeoutCheckInterval); 272 } 273 236 274 // Start Server-Sent Events connection 237 275 try { … … 256 294 257 295 eventSource = new EventSource(url); 258 296 259 297 eventSource.onopen = function() { 260 298 addStatusMessage('Connected to generation service', 'info'); 299 lastMessageTime = Date.now(); 300 301 // Start timeout monitoring - if no message for 3 minutes, assume something went wrong 302 timeoutCheckInterval = setInterval(function() { 303 var timeSinceLastMessage = Date.now() - lastMessageTime; 304 var maxIdleTime = 180000; // 3 minutes in milliseconds 305 306 if (timeSinceLastMessage > maxIdleTime && generationInProgress) { 307 addStatusMessage('❌ Server stopped responding. The generation may have failed.', 'error'); 308 if (progressInterval) clearInterval(progressInterval); 309 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 310 if (eventSource) { 311 eventSource.close(); 312 eventSource = null; 313 } 314 resetUI(false); 315 } 316 }, 10000); // Check every 10 seconds 261 317 }; 262 318 263 319 eventSource.onmessage = function(event) { 320 lastMessageTime = Date.now(); // Update last message time 264 321 try { 265 322 var data = JSON.parse(event.data); … … 272 329 $progressBar.css('width', '100%').text('100%'); 273 330 addStatusMessage('✓ ' + data.message, 'success'); 274 331 275 332 // Show the audio preview instead of reloading 276 333 if (data.audio_url) { … … 278 335 addStatusMessage('Audio preview is ready!', 'success'); 279 336 } 280 337 281 338 // Update credit display after successful generation 282 339 updateCreditDisplay(); 283 340 284 341 // Instead of full resetUI, specifically manage button visibility 285 342 generationInProgress = false; // Allow new actions … … 293 350 294 351 // Explicitly close the connection on success from client-side 352 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 295 353 if (eventSource) { 296 354 addStatusMessage('Closing SSE connection from client after success.', 'info'); … … 298 356 eventSource = null; // Prevent resetUI or onerror from trying to close it again 299 357 } 300 358 301 359 } else if (data.status === 'error') { 360 // Stop the simulated progress immediately on error 361 if (progressInterval) clearInterval(progressInterval); 362 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 363 // Do NOT set progress to 100% on error 302 364 addStatusMessage('❌ Error: ' + data.message, 'error'); 303 resetUI( true); // Pass true to indicate success, so progress bar stays at 100% briefly365 resetUI(false); // Pass false to indicate error - this will hide progress bar and reset UI 304 366 } 305 367 … … 314 376 eventSource.onerror = function(error) { 315 377 console.error('SSE Error:', error); 378 // Stop the simulated progress immediately 379 if (progressInterval) clearInterval(progressInterval); 380 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 381 316 382 if (eventSource.readyState === EventSource.CLOSED) { 317 addStatusMessage('Connection to server was closed', 'error'); 383 addStatusMessage('❌ Connection to server was closed. The server may have encountered an error.', 'error'); 384 } else if (eventSource.readyState === EventSource.CONNECTING) { 385 addStatusMessage('❌ Connection error while attempting to reconnect. Please try again.', 'error'); 318 386 } else { 319 addStatusMessage(' Connection error. Pleasetry again.', 'error');387 addStatusMessage('❌ Connection error. Please check your internet connection and try again.', 'error'); 320 388 } 321 if (progressInterval) clearInterval(progressInterval); 389 322 390 resetUI(false); // Pass false to indicate error/connection close 323 391 }; … … 366 434 } 367 435 368 function resetUI(isSuccess) { 436 function resetUI(isSuccess) { 369 437 generationInProgress = false; 370 438 $spinner.removeClass('is-active'); 371 if (progressInterval) clearInterval(progressInterval); 439 if (progressInterval) { 440 clearInterval(progressInterval); 441 progressInterval = null; 442 } 443 if (timeoutCheckInterval) { 444 clearInterval(timeoutCheckInterval); 445 timeoutCheckInterval = null; 446 } 372 447 373 448 if (!isSuccess) { // Only hide progress bar immediately if not a success case … … 377 452 // On success, the bar is at 100%. It will be hidden/reset if user navigates or starts new generation. 378 453 379 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 454 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 380 455 ($('.post2podcast-player audio source').length > 0 && $('.post2podcast-player audio source').attr('src') !== ''); 381 456 … … 389 464 $('#delete_podcast').hide(); 390 465 } 391 466 392 467 if (eventSource) { 393 468 eventSource.close(); … … 458 533 eventSource.close(); 459 534 } 535 if (timeoutCheckInterval) { 536 clearInterval(timeoutCheckInterval); 537 } 460 538 }); 461 539 -
post2podcast/tags/1.2.7/includes/class-post2podcast-api.php
r3437853 r3437893 328 328 } 329 329 if (!isset($job_response['audio_content'])) { 330 throw new Exception('Audio content not found in API response.'); 330 $error_detail = isset($job_response['detail']) ? $job_response['detail'] : 'Unknown error'; 331 throw new Exception('Audio generation failed on server: ' . $error_detail); 331 332 } 332 333 $audio_content = base64_decode($job_response['audio_content']); … … 682 683 $error_message = $response->get_error_message(); 683 684 error_log("Post2Podcast WP HTTP API Error (post2podcast_start_sync_generation): " . $error_message); 684 throw new Exception('API request failed (WP HTTP API error): ' . esc_html($error_message));685 return new WP_Error('connection_error', 'Connection error: ' . $error_message); 685 686 } 686 687 … … 690 691 if ($status_code >= 400) { 691 692 error_log("Post2Podcast API Error (post2podcast_start_sync_generation): Status " . $status_code . " - Response: " . $response_body); 692 throw new Exception('API request failed with status code ' . esc_html($status_code) . ': ' . esc_html($response_body)); 693 } 694 return json_decode($response_body, true); 693 694 // Try to parse error details from response 695 $error_data = json_decode($response_body, true); 696 $error_message = isset($error_data['detail']) ? $error_data['detail'] : $response_body; 697 698 return new WP_Error('api_error', 'Server error (' . $status_code . '): ' . $error_message); 699 } 700 701 $decoded = json_decode($response_body, true); 702 703 // Check if the response indicates a server-side error even with 200 status 704 if (!$decoded || (isset($decoded['error']) && $decoded['error'])) { 705 $error_msg = isset($decoded['error']) ? $decoded['error'] : 'Invalid response from server'; 706 error_log("Post2Podcast API Error: " . $error_msg); 707 return new WP_Error('generation_failed', $error_msg); 708 } 709 710 return $decoded; 695 711 } 696 712 -
post2podcast/tags/1.2.7/post2podcast.php
r3437853 r3437893 4 4 * Plugin server URI: https://github.com/samukbg/post2podcast-server 5 5 * Description: Convert WordPress posts into podcast episodes with AI voices 6 * Version: 1.2. 66 * Version: 1.2.7 7 7 * Author: Samuel Bezerra 8 8 * Author URI: https://github.com/samukbg … … 20 20 21 21 // Define plugin constants 22 define('POST2PODCAST_VERSION', '1.2. 6');22 define('POST2PODCAST_VERSION', '1.2.7'); 23 23 define('POST2PODCAST_PLUGIN_DIR', plugin_dir_path(__FILE__)); 24 24 define('POST2PODCAST_PLUGIN_URL', plugin_dir_url(__FILE__)); -
post2podcast/tags/1.2.7/readme.txt
r3437853 r3437893 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 67 Stable tag: 1.2.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 274 274 == Changelog == 275 275 276 = 1.2.7 = 277 * FIXED: Error handling now properly detects and reports server failures instead of showing 100% progress 278 * FIXED: Language detection now automatically selects appropriate Kokoro TTS voices in dropdown menus 279 * IMPROVED: Automatic server fallback - when one TTS server fails, automatically tries the next available server 280 * IMPROVED: Added 3-minute timeout detection to catch when server stops responding during generation 281 * IMPROVED: Better error messages showing specific failure reasons (connection errors, server errors, timeouts) 282 * IMPROVED: Progress bar now correctly stops on errors instead of completing to 100% 283 * ENHANCED: Backend now uses Kokoro TTS voice codes for all language-specific voice recommendations 284 276 285 = 1.2.6 = 277 286 * IMPROVED: Minor bug fixes and performance improvements. -
post2podcast/trunk/assets/js/admin.js
r3331992 r3437893 14 14 var generationInProgress = false; 15 15 var progressInterval = null; // For simulated progress 16 var lastMessageTime = null; // Track when we last received a message 17 var timeoutCheckInterval = null; // For checking message timeout 16 18 17 19 // Handle delete podcast button click (for existing audio) … … 39 41 eventSource = null; 40 42 } 41 43 42 44 if (progressInterval) { 43 45 clearInterval(progressInterval); 44 46 progressInterval = null; 45 47 } 46 48 49 if (timeoutCheckInterval) { 50 clearInterval(timeoutCheckInterval); 51 timeoutCheckInterval = null; 52 } 53 47 54 generationInProgress = false; 48 55 … … 144 151 if (response.success) { 145 152 var detection = response.data.detection_result; 146 153 147 154 if (detection.language_code) { 148 155 // Update the UI with detected language 149 var languageInfo = '<p><strong>Detected Language:</strong> ' + 150 (detection.language_name || detection.language_code) + 156 var languageInfo = '<p><strong>Detected Language:</strong> ' + 157 (detection.language_name || detection.language_code) + 151 158 ' <span class="language-code">(' + detection.language_code + ')</span></p>'; 152 languageInfo += '<p class="description">Voices will be automatically selected based on detected language.</p>'; 153 159 154 160 $('#detected-language-info').html(languageInfo); 155 156 // Showsuggested voices if available161 162 // Automatically select suggested voices if available 157 163 if (detection.suggested_voices) { 158 164 console.log('Suggested voices for ' + detection.language_code + ':', detection.suggested_voices); 165 166 var voice1 = detection.suggested_voices.voice1; 167 var voice2 = detection.suggested_voices.voice2; 168 169 // Set the voice selectors to the suggested voices 170 var $voice1Select = $('#post2podcast_voice1'); 171 var $voice2Select = $('#post2podcast_voice2'); 172 173 // Check if the voices exist in the dropdown and select them 174 if ($voice1Select.find('option[value="' + voice1 + '"]').length > 0) { 175 $voice1Select.val(voice1); 176 console.log('Selected voice1: ' + voice1); 177 } else { 178 console.warn('Voice ' + voice1 + ' not found in dropdown'); 179 } 180 181 if ($voice2Select.find('option[value="' + voice2 + '"]').length > 0) { 182 $voice2Select.val(voice2); 183 console.log('Selected voice2: ' + voice2); 184 } else { 185 console.warn('Voice ' + voice2 + ' not found in dropdown'); 186 } 187 188 // Update the language info to show selected voices 189 languageInfo += '<p class="description" style="color: #28a745; font-weight: 500;">✓ Voices automatically selected: ' + 190 voice1 + ' and ' + voice2 + '</p>'; 191 $('#detected-language-info').html(languageInfo); 192 } else { 193 languageInfo += '<p class="description">Please select appropriate voices for this language.</p>'; 194 $('#detected-language-info').html(languageInfo); 159 195 } 160 196 } else { … … 230 266 eventSource.close(); 231 267 } 232 233 // Create a timestamp for this generation session 234 var sessionId = 'session-' + Date.now(); 235 268 269 // Clear any existing timeout check 270 if (timeoutCheckInterval) { 271 clearInterval(timeoutCheckInterval); 272 } 273 236 274 // Start Server-Sent Events connection 237 275 try { … … 256 294 257 295 eventSource = new EventSource(url); 258 296 259 297 eventSource.onopen = function() { 260 298 addStatusMessage('Connected to generation service', 'info'); 299 lastMessageTime = Date.now(); 300 301 // Start timeout monitoring - if no message for 3 minutes, assume something went wrong 302 timeoutCheckInterval = setInterval(function() { 303 var timeSinceLastMessage = Date.now() - lastMessageTime; 304 var maxIdleTime = 180000; // 3 minutes in milliseconds 305 306 if (timeSinceLastMessage > maxIdleTime && generationInProgress) { 307 addStatusMessage('❌ Server stopped responding. The generation may have failed.', 'error'); 308 if (progressInterval) clearInterval(progressInterval); 309 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 310 if (eventSource) { 311 eventSource.close(); 312 eventSource = null; 313 } 314 resetUI(false); 315 } 316 }, 10000); // Check every 10 seconds 261 317 }; 262 318 263 319 eventSource.onmessage = function(event) { 320 lastMessageTime = Date.now(); // Update last message time 264 321 try { 265 322 var data = JSON.parse(event.data); … … 272 329 $progressBar.css('width', '100%').text('100%'); 273 330 addStatusMessage('✓ ' + data.message, 'success'); 274 331 275 332 // Show the audio preview instead of reloading 276 333 if (data.audio_url) { … … 278 335 addStatusMessage('Audio preview is ready!', 'success'); 279 336 } 280 337 281 338 // Update credit display after successful generation 282 339 updateCreditDisplay(); 283 340 284 341 // Instead of full resetUI, specifically manage button visibility 285 342 generationInProgress = false; // Allow new actions … … 293 350 294 351 // Explicitly close the connection on success from client-side 352 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 295 353 if (eventSource) { 296 354 addStatusMessage('Closing SSE connection from client after success.', 'info'); … … 298 356 eventSource = null; // Prevent resetUI or onerror from trying to close it again 299 357 } 300 358 301 359 } else if (data.status === 'error') { 360 // Stop the simulated progress immediately on error 361 if (progressInterval) clearInterval(progressInterval); 362 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 363 // Do NOT set progress to 100% on error 302 364 addStatusMessage('❌ Error: ' + data.message, 'error'); 303 resetUI( true); // Pass true to indicate success, so progress bar stays at 100% briefly365 resetUI(false); // Pass false to indicate error - this will hide progress bar and reset UI 304 366 } 305 367 … … 314 376 eventSource.onerror = function(error) { 315 377 console.error('SSE Error:', error); 378 // Stop the simulated progress immediately 379 if (progressInterval) clearInterval(progressInterval); 380 if (timeoutCheckInterval) clearInterval(timeoutCheckInterval); 381 316 382 if (eventSource.readyState === EventSource.CLOSED) { 317 addStatusMessage('Connection to server was closed', 'error'); 383 addStatusMessage('❌ Connection to server was closed. The server may have encountered an error.', 'error'); 384 } else if (eventSource.readyState === EventSource.CONNECTING) { 385 addStatusMessage('❌ Connection error while attempting to reconnect. Please try again.', 'error'); 318 386 } else { 319 addStatusMessage(' Connection error. Pleasetry again.', 'error');387 addStatusMessage('❌ Connection error. Please check your internet connection and try again.', 'error'); 320 388 } 321 if (progressInterval) clearInterval(progressInterval); 389 322 390 resetUI(false); // Pass false to indicate error/connection close 323 391 }; … … 366 434 } 367 435 368 function resetUI(isSuccess) { 436 function resetUI(isSuccess) { 369 437 generationInProgress = false; 370 438 $spinner.removeClass('is-active'); 371 if (progressInterval) clearInterval(progressInterval); 439 if (progressInterval) { 440 clearInterval(progressInterval); 441 progressInterval = null; 442 } 443 if (timeoutCheckInterval) { 444 clearInterval(timeoutCheckInterval); 445 timeoutCheckInterval = null; 446 } 372 447 373 448 if (!isSuccess) { // Only hide progress bar immediately if not a success case … … 377 452 // On success, the bar is at 100%. It will be hidden/reset if user navigates or starts new generation. 378 453 379 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 454 var audioExists = ($('#preview-audio-source').attr('src') && $('#preview-audio-source').attr('src') !== '') || 380 455 ($('.post2podcast-player audio source').length > 0 && $('.post2podcast-player audio source').attr('src') !== ''); 381 456 … … 389 464 $('#delete_podcast').hide(); 390 465 } 391 466 392 467 if (eventSource) { 393 468 eventSource.close(); … … 458 533 eventSource.close(); 459 534 } 535 if (timeoutCheckInterval) { 536 clearInterval(timeoutCheckInterval); 537 } 460 538 }); 461 539 -
post2podcast/trunk/includes/class-post2podcast-api.php
r3437853 r3437893 328 328 } 329 329 if (!isset($job_response['audio_content'])) { 330 throw new Exception('Audio content not found in API response.'); 330 $error_detail = isset($job_response['detail']) ? $job_response['detail'] : 'Unknown error'; 331 throw new Exception('Audio generation failed on server: ' . $error_detail); 331 332 } 332 333 $audio_content = base64_decode($job_response['audio_content']); … … 682 683 $error_message = $response->get_error_message(); 683 684 error_log("Post2Podcast WP HTTP API Error (post2podcast_start_sync_generation): " . $error_message); 684 throw new Exception('API request failed (WP HTTP API error): ' . esc_html($error_message));685 return new WP_Error('connection_error', 'Connection error: ' . $error_message); 685 686 } 686 687 … … 690 691 if ($status_code >= 400) { 691 692 error_log("Post2Podcast API Error (post2podcast_start_sync_generation): Status " . $status_code . " - Response: " . $response_body); 692 throw new Exception('API request failed with status code ' . esc_html($status_code) . ': ' . esc_html($response_body)); 693 } 694 return json_decode($response_body, true); 693 694 // Try to parse error details from response 695 $error_data = json_decode($response_body, true); 696 $error_message = isset($error_data['detail']) ? $error_data['detail'] : $response_body; 697 698 return new WP_Error('api_error', 'Server error (' . $status_code . '): ' . $error_message); 699 } 700 701 $decoded = json_decode($response_body, true); 702 703 // Check if the response indicates a server-side error even with 200 status 704 if (!$decoded || (isset($decoded['error']) && $decoded['error'])) { 705 $error_msg = isset($decoded['error']) ? $decoded['error'] : 'Invalid response from server'; 706 error_log("Post2Podcast API Error: " . $error_msg); 707 return new WP_Error('generation_failed', $error_msg); 708 } 709 710 return $decoded; 695 711 } 696 712 -
post2podcast/trunk/post2podcast.php
r3437853 r3437893 4 4 * Plugin server URI: https://github.com/samukbg/post2podcast-server 5 5 * Description: Convert WordPress posts into podcast episodes with AI voices 6 * Version: 1.2. 66 * Version: 1.2.7 7 7 * Author: Samuel Bezerra 8 8 * Author URI: https://github.com/samukbg … … 20 20 21 21 // Define plugin constants 22 define('POST2PODCAST_VERSION', '1.2. 6');22 define('POST2PODCAST_VERSION', '1.2.7'); 23 23 define('POST2PODCAST_PLUGIN_DIR', plugin_dir_path(__FILE__)); 24 24 define('POST2PODCAST_PLUGIN_URL', plugin_dir_url(__FILE__)); -
post2podcast/trunk/readme.txt
r3437853 r3437893 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 67 Stable tag: 1.2.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 274 274 == Changelog == 275 275 276 = 1.2.7 = 277 * FIXED: Error handling now properly detects and reports server failures instead of showing 100% progress 278 * FIXED: Language detection now automatically selects appropriate Kokoro TTS voices in dropdown menus 279 * IMPROVED: Automatic server fallback - when one TTS server fails, automatically tries the next available server 280 * IMPROVED: Added 3-minute timeout detection to catch when server stops responding during generation 281 * IMPROVED: Better error messages showing specific failure reasons (connection errors, server errors, timeouts) 282 * IMPROVED: Progress bar now correctly stops on errors instead of completing to 100% 283 * ENHANCED: Backend now uses Kokoro TTS voice codes for all language-specific voice recommendations 284 276 285 = 1.2.6 = 277 286 * IMPROVED: Minor bug fixes and performance improvements.
Note: See TracChangeset
for help on using the changeset viewer.