Toowired commited on
Commit
986dff9
·
verified ·
1 Parent(s): d67da3e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +190 -41
index.html CHANGED
@@ -99,9 +99,25 @@
99
  <i class="fas fa-save mr-2"></i> Save Key
100
  </button>
101
  </div>
102
- <p class="text-xs text-blue-600 mt-2">
103
- <i class="fas fa-info-circle mr-1"></i> Your API key is stored locally and never sent to our servers.
104
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
 
107
  <!-- Input Section -->
@@ -321,13 +337,47 @@
321
  document.head.appendChild(style);
322
 
323
  // API Key Management
324
- saveKeyBtn.addEventListener('click', () => {
325
  const key = apiKeyInput.value.trim();
326
  if (key) {
327
- apiKey = key;
328
- localStorage.setItem('googleTTSApiKey', key);
329
- loadVoices();
330
- showToast('API key saved successfully', 'success');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  } else {
332
  showToast('Please enter a valid API key', 'error');
333
  }
@@ -355,29 +405,81 @@
355
 
356
  try {
357
  const languageCode = languageSelect.value;
358
- const response = await fetch(`https://texttospeech.googleapis.com/v1/voices?key=${apiKey}&languageCode=${languageCode}`);
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  if (!response.ok) {
 
 
 
 
 
 
 
 
 
361
  if (response.status === 403) {
362
- throw new Error('Invalid API key or Text-to-Speech API not enabled');
363
  } else if (response.status === 400) {
364
- throw new Error('Invalid request. Check your API key.');
 
 
 
 
 
 
365
  } else {
366
- throw new Error(`API error: ${response.status} ${response.statusText}`);
367
  }
368
  }
369
 
370
  const data = await response.json();
371
- renderVoiceOptions(data.voices || []);
 
 
 
 
 
372
  } catch (error) {
373
  console.error('Error loading voices:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  voiceGrid.innerHTML = `
375
  <div class="col-span-full text-center py-8">
376
  <i class="fas fa-exclamation-triangle text-red-400 text-2xl"></i>
377
  <p class="text-gray-500 mt-2">Failed to load voices</p>
378
- <p class="text-red-500 text-sm mt-1">${error.message}</p>
379
- <button id="retryVoices" class="mt-2 text-blue-500 hover:text-blue-700 text-sm">
380
- <i class="fas fa-retry mr-1"></i> Retry
 
381
  </button>
382
  </div>
383
  `;
@@ -511,36 +613,83 @@
511
  throw new Error('API key not provided');
512
  }
513
 
514
- const request = {
515
- input: { text: text },
516
- voice: {
517
- languageCode: voice.languageCodes[0],
518
- name: voice.name,
519
- ssmlGender: voice.ssmlGender
520
- },
521
- audioConfig: {
522
- audioEncoding: 'MP3',
523
- speakingRate: rate,
524
- pitch: pitch,
525
- effectsProfileId: model === 'studio' ? ['telephony-class-application'] : undefined
526
- }
527
  };
528
 
529
- const response = await fetch(`https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`, {
530
- method: 'POST',
531
- headers: {
532
- 'Content-Type': 'application/json',
533
- },
534
- body: JSON.stringify(request)
535
- });
536
 
537
- if (!response.ok) {
538
- const errorData = await response.json();
539
- throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
540
  }
541
 
542
- const data = await response.json();
543
- return data.audioContent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  }
545
 
546
  // Play audio data
 
99
  <i class="fas fa-save mr-2"></i> Save Key
100
  </button>
101
  </div>
102
+ <div class="mt-3 text-xs text-blue-600">
103
+ <p class="flex items-center mb-1">
104
+ <i class="fas fa-info-circle mr-1"></i> Your API key is stored locally and never sent to our servers.
105
+ </p>
106
+ <details class="mt-2">
107
+ <summary class="cursor-pointer hover:text-blue-800">Need help getting an API key? (Click to expand)</summary>
108
+ <div class="mt-2 text-xs text-gray-600 bg-white p-3 rounded border">
109
+ <p class="font-medium">How to get your Google Cloud API key:</p>
110
+ <ol class="mt-1 list-decimal list-inside space-y-1">
111
+ <li>Go to <a href="https://console.cloud.google.com" target="_blank" class="text-blue-600 hover:underline">Google Cloud Console</a></li>
112
+ <li>Create a new project or select existing one</li>
113
+ <li>Enable the "Cloud Text-to-Speech API"</li>
114
+ <li>Go to "Credentials" → "Create Credentials" → "API Key"</li>
115
+ <li>Copy the generated API key and paste it above</li>
116
+ <li>Optionally, restrict the key to Text-to-Speech API for security</li>
117
+ </ol>
118
+ </div>
119
+ </details>
120
+ </div>
121
  </div>
122
 
123
  <!-- Input Section -->
 
337
  document.head.appendChild(style);
338
 
339
  // API Key Management
340
+ saveKeyBtn.addEventListener('click', async () => {
341
  const key = apiKeyInput.value.trim();
342
  if (key) {
343
+ // Show loading state
344
+ saveKeyBtn.innerHTML = '<div class="loading-spinner inline-block w-4 h-4 mr-2"></div>Validating...';
345
+ saveKeyBtn.disabled = true;
346
+
347
+ try {
348
+ // Validate API key by making a simple request
349
+ const testUrl = `https://texttospeech.googleapis.com/v1/voices?key=${encodeURIComponent(key)}&languageCode=en-US&pageSize=1`;
350
+ const response = await fetch(testUrl, {
351
+ method: 'GET',
352
+ headers: {
353
+ 'Accept': 'application/json',
354
+ },
355
+ mode: 'cors',
356
+ cache: 'no-cache'
357
+ });
358
+
359
+ if (response.ok) {
360
+ apiKey = key;
361
+ localStorage.setItem('googleTTSApiKey', key);
362
+ showToast('API key validated and saved successfully', 'success');
363
+ loadVoices();
364
+ } else {
365
+ let errorMessage = 'Invalid API key';
366
+ if (response.status === 403) {
367
+ errorMessage = 'API key invalid or Text-to-Speech API not enabled';
368
+ } else if (response.status === 400) {
369
+ errorMessage = 'Invalid API key format';
370
+ }
371
+ showToast(errorMessage, 'error');
372
+ }
373
+ } catch (error) {
374
+ console.error('API key validation error:', error);
375
+ showToast('Failed to validate API key. Please check your internet connection.', 'error');
376
+ } finally {
377
+ // Reset button state
378
+ saveKeyBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Save Key';
379
+ saveKeyBtn.disabled = false;
380
+ }
381
  } else {
382
  showToast('Please enter a valid API key', 'error');
383
  }
 
405
 
406
  try {
407
  const languageCode = languageSelect.value;
 
408
 
409
+ // Use different API endpoint format to avoid CORS issues
410
+ const url = `https://texttospeech.googleapis.com/v1/voices?key=${encodeURIComponent(apiKey)}&languageCode=${languageCode}`;
411
+
412
+ const response = await fetch(url, {
413
+ method: 'GET',
414
+ headers: {
415
+ 'Accept': 'application/json',
416
+ 'Content-Type': 'application/json',
417
+ },
418
+ mode: 'cors',
419
+ cache: 'no-cache'
420
+ });
421
+
422
+ // Better error handling
423
  if (!response.ok) {
424
+ let errorMessage = 'Unknown error occurred';
425
+ try {
426
+ const errorData = await response.json();
427
+ errorMessage = errorData.error?.message || errorMessage;
428
+ } catch (e) {
429
+ // If response is not JSON, use status text
430
+ errorMessage = response.statusText || `HTTP ${response.status}`;
431
+ }
432
+
433
  if (response.status === 403) {
434
+ throw new Error('Invalid API key or Text-to-Speech API not enabled. Please check your Google Cloud console.');
435
  } else if (response.status === 400) {
436
+ throw new Error('Bad request. Please verify your API key format is correct.');
437
+ } else if (response.status === 401) {
438
+ throw new Error('Unauthorized. Please check your API key permissions.');
439
+ } else if (response.status === 404) {
440
+ throw new Error('API endpoint not found. Please check your API key validity.');
441
+ } else if (response.status >= 500) {
442
+ throw new Error('Google Cloud service error. Please try again later.');
443
  } else {
444
+ throw new Error(`API error (${response.status}): ${errorMessage}`);
445
  }
446
  }
447
 
448
  const data = await response.json();
449
+
450
+ if (!data || !data.voices) {
451
+ throw new Error('Invalid response from Google TTS API');
452
+ }
453
+
454
+ renderVoiceOptions(data.voices);
455
  } catch (error) {
456
  console.error('Error loading voices:', error);
457
+
458
+ let errorDisplay = error.message;
459
+ let troubleshootingTips = '';
460
+
461
+ if (error.message.includes('Invalid API key')) {
462
+ troubleshootingTips = `
463
+ <div class="mt-3 text-xs text-gray-600">
464
+ <p class="font-medium">Troubleshooting tips:</p>
465
+ <ul class="mt-1 list-disc list-inside space-y-1">
466
+ <li>Go to Google Cloud Console</li>
467
+ <li>Enable the Text-to-Speech API</li>
468
+ <li>Create API credentials</li>
469
+ <li>Copy the API key exactly</li>
470
+ </ul>
471
+ </div>
472
+ `;
473
+ }
474
+
475
  voiceGrid.innerHTML = `
476
  <div class="col-span-full text-center py-8">
477
  <i class="fas fa-exclamation-triangle text-red-400 text-2xl"></i>
478
  <p class="text-gray-500 mt-2">Failed to load voices</p>
479
+ <p class="text-red-500 text-sm mt-1">${errorDisplay}</p>
480
+ ${troubleshootingTips}
481
+ <button id="retryVoices" class="mt-4 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded text-sm">
482
+ <i class="fas fa-sync-alt mr-1"></i> Retry
483
  </button>
484
  </div>
485
  `;
 
613
  throw new Error('API key not provided');
614
  }
615
 
616
+ if (!text || !text.trim()) {
617
+ throw new Error('No text provided for synthesis');
618
+ }
619
+
620
+ // Prepare voice configuration
621
+ let voiceConfig = {
622
+ languageCode: voice.languageCodes[0],
623
+ name: voice.name,
624
+ ssmlGender: voice.ssmlGender
 
 
 
 
625
  };
626
 
627
+ // Prepare audio configuration
628
+ let audioConfig = {
629
+ audioEncoding: 'MP3',
630
+ speakingRate: Math.max(0.25, Math.min(4.0, rate)), // Clamp between 0.25 and 4.0
631
+ pitch: Math.max(-20.0, Math.min(20.0, pitch)), // Clamp between -20 and 20
632
+ };
 
633
 
634
+ // Add effects profile for studio quality
635
+ if (model === 'studio' && voice.name.includes('Studio')) {
636
+ audioConfig.effectsProfileId = ['telephony-class-application'];
637
  }
638
 
639
+ const request = {
640
+ input: { text: text.trim() },
641
+ voice: voiceConfig,
642
+ audioConfig: audioConfig
643
+ };
644
+
645
+ try {
646
+ const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${encodeURIComponent(apiKey)}`;
647
+
648
+ const response = await fetch(url, {
649
+ method: 'POST',
650
+ headers: {
651
+ 'Content-Type': 'application/json',
652
+ 'Accept': 'application/json',
653
+ },
654
+ mode: 'cors',
655
+ cache: 'no-cache',
656
+ body: JSON.stringify(request)
657
+ });
658
+
659
+ if (!response.ok) {
660
+ let errorMessage = 'Failed to synthesize speech';
661
+
662
+ try {
663
+ const errorData = await response.json();
664
+ errorMessage = errorData.error?.message || errorMessage;
665
+ } catch (e) {
666
+ errorMessage = `HTTP ${response.status}: ${response.statusText}`;
667
+ }
668
+
669
+ if (response.status === 400) {
670
+ throw new Error('Invalid request parameters. Please check your text and settings.');
671
+ } else if (response.status === 403) {
672
+ throw new Error('Access denied. Please check your API key and quota.');
673
+ } else if (response.status === 429) {
674
+ throw new Error('Rate limit exceeded. Please wait a moment and try again.');
675
+ } else if (response.status >= 500) {
676
+ throw new Error('Google Cloud service error. Please try again later.');
677
+ } else {
678
+ throw new Error(errorMessage);
679
+ }
680
+ }
681
+
682
+ const data = await response.json();
683
+
684
+ if (!data || !data.audioContent) {
685
+ throw new Error('Invalid response from Google TTS API');
686
+ }
687
+
688
+ return data.audioContent;
689
+ } catch (error) {
690
+ console.error('Synthesis error:', error);
691
+ throw error;
692
+ }
693
  }
694
 
695
  // Play audio data