Toowired commited on
Commit
fd79a69
Β·
verified Β·
1 Parent(s): 4d4e4d6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +150 -48
index.html CHANGED
@@ -92,8 +92,13 @@
92
  <div class="flex flex-col md:flex-row items-center gap-4">
93
  <div class="flex-1">
94
  <label for="apiKey" class="block text-sm font-medium text-blue-800 mb-1">Google Cloud API Key</label>
95
- <input type="password" id="apiKey" placeholder="Enter your Google Cloud API key"
96
- class="api-key-input w-full px-4 py-2 border border-blue-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
 
 
 
 
 
97
  </div>
98
  <button id="saveKeyBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-md transition whitespace-nowrap">
99
  <i class="fas fa-save mr-2"></i> Save Key
@@ -115,6 +120,14 @@
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>
@@ -307,6 +320,20 @@
307
 
308
  // Initialize AudioContext on user interaction
309
  document.addEventListener('click', initAudioContext, { once: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  }
311
 
312
  function initAudioContext() {
@@ -345,72 +372,123 @@
345
  saveKeyBtn.disabled = true;
346
 
347
  try {
348
- // Validate API key by making a simple request
349
- // Method 1: Try with header approach first
350
- let response;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  try {
352
- response = await fetch(`https://texttospeech.googleapis.com/v1/voices?languageCode=en-US&pageSize=1`, {
 
353
  method: 'GET',
354
  headers: {
355
- 'X-Goog-Api-Key': key,
356
  'Accept': 'application/json',
357
  },
358
  mode: 'cors',
359
  cache: 'no-cache'
360
  });
 
 
 
 
 
 
 
 
 
361
  } catch (headerError) {
362
- // Method 2: Fallback to URL parameter
363
- const testUrl = `https://texttospeech.googleapis.com/v1/voices?key=${encodeURIComponent(key)}&languageCode=en-US&pageSize=1`;
364
- response = await fetch(testUrl, {
365
- method: 'GET',
366
- headers: {
367
- 'Accept': 'application/json',
368
- },
369
- mode: 'cors',
370
- cache: 'no-cache'
371
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
373
 
374
- if (response.ok) {
375
- apiKey = key;
376
- localStorage.setItem('googleTTSApiKey', key);
377
  showToast('API key validated and saved successfully', 'success');
378
  loadVoices();
379
  } else {
380
- let errorMessage = 'API key validation failed';
381
- let details = '';
382
 
383
- try {
384
- const errorData = await response.json();
385
- errorMessage = errorData.error?.message || errorMessage;
386
- details = errorData.error?.details ? ` (${JSON.stringify(errorData.error.details)})` : '';
387
- } catch (e) {
388
- details = ` (HTTP ${response.status})`;
 
389
  }
390
 
391
- if (response.status === 403) {
392
- errorMessage = 'Access denied. Check: 1) API key validity, 2) Text-to-Speech API enabled, 3) Billing enabled, 4) Key restrictions';
393
- } else if (response.status === 400) {
394
- errorMessage = 'Bad request. API key format might be incorrect';
395
- } else if (response.status === 429) {
396
- errorMessage = 'Too many requests. Please wait a moment';
397
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
- showToast(errorMessage + details, 'error', 5000);
 
400
  }
401
  } catch (error) {
402
- console.error('API key validation error:', error);
403
- let errorMessage = 'Failed to validate API key. ';
404
-
405
- if (error.message.includes('Failed to fetch')) {
406
- errorMessage += 'Please check your internet connection.';
407
- } else if (error.message.includes('CORS')) {
408
- errorMessage += 'CORS policy issue. Try with a different browser or disable extensions.';
409
- } else {
410
- errorMessage += error.message;
411
- }
412
-
413
- showToast(errorMessage, 'error', 5000);
414
  } finally {
415
  // Reset button state
416
  saveKeyBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Save Key';
@@ -421,6 +499,30 @@
421
  }
422
  });
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  // Load available voices from Google TTS
425
  async function loadVoices() {
426
  if (!apiKey) {
 
92
  <div class="flex flex-col md:flex-row items-center gap-4">
93
  <div class="flex-1">
94
  <label for="apiKey" class="block text-sm font-medium text-blue-800 mb-1">Google Cloud API Key</label>
95
+ <div class="relative">
96
+ <input type="password" id="apiKey" placeholder="Enter your Google Cloud API key"
97
+ class="api-key-input w-full px-4 py-2 pr-10 border border-blue-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
98
+ <button type="button" id="toggleKeyVisibility" class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600">
99
+ <i class="fas fa-eye" id="eyeIcon"></i>
100
+ </button>
101
+ </div>
102
  </div>
103
  <button id="saveKeyBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-md transition whitespace-nowrap">
104
  <i class="fas fa-save mr-2"></i> Save Key
 
120
  <li>Copy the generated API key and paste it above</li>
121
  <li>Optionally, restrict the key to Text-to-Speech API for security</li>
122
  </ol>
123
+ <div class="mt-3 p-2 bg-yellow-50 border border-yellow-200 rounded text-yellow-800">
124
+ <p class="font-medium">⚠️ Important:</p>
125
+ <ul class="mt-1 list-disc list-inside space-y-1">
126
+ <li>Make sure billing is enabled for your project</li>
127
+ <li>Copy ONLY the API key (no extra text)</li>
128
+ <li>If restricted, allow this domain: ${window.location.hostname}</li>
129
+ </ul>
130
+ </div>
131
  </div>
132
  </details>
133
  </div>
 
320
 
321
  // Initialize AudioContext on user interaction
322
  document.addEventListener('click', initAudioContext, { once: true });
323
+
324
+ // API Key visibility toggle
325
+ const toggleKeyVisibility = document.getElementById('toggleKeyVisibility');
326
+ const eyeIcon = document.getElementById('eyeIcon');
327
+
328
+ toggleKeyVisibility.addEventListener('click', () => {
329
+ if (apiKeyInput.type === 'password') {
330
+ apiKeyInput.type = 'text';
331
+ eyeIcon.className = 'fas fa-eye-slash';
332
+ } else {
333
+ apiKeyInput.type = 'password';
334
+ eyeIcon.className = 'fas fa-eye';
335
+ }
336
+ });
337
  }
338
 
339
  function initAudioContext() {
 
372
  saveKeyBtn.disabled = true;
373
 
374
  try {
375
+ // Clean and validate the API key format
376
+ const cleanApiKey = key.trim();
377
+
378
+ // Basic validation of API key format
379
+ if (!cleanApiKey || cleanApiKey.length < 10) {
380
+ throw new Error('API key appears to be too short. Please verify you copied the complete key.');
381
+ }
382
+
383
+ // Check for common API key formats
384
+ if (!cleanApiKey.match(/^[A-Za-z0-9_-]+$/)) {
385
+ throw new Error('API key contains invalid characters. Please copy only the key without any extra text.');
386
+ }
387
+
388
+ // Multiple validation attempts with different methods
389
+ let validationErrors = [];
390
+ let validationSuccess = false;
391
+
392
+ // Method 1: Header authentication with minimal request
393
  try {
394
+ console.log('Attempting validation with X-Goog-Api-Key header...');
395
+ const response = await fetch(`https://texttospeech.googleapis.com/v1/voices?languageCode=en-US&pageSize=1`, {
396
  method: 'GET',
397
  headers: {
398
+ 'X-Goog-Api-Key': cleanApiKey,
399
  'Accept': 'application/json',
400
  },
401
  mode: 'cors',
402
  cache: 'no-cache'
403
  });
404
+
405
+ if (response.ok) {
406
+ validationSuccess = true;
407
+ const data = await response.json();
408
+ console.log('Header method successful:', data);
409
+ } else {
410
+ const errorText = await response.text();
411
+ validationErrors.push(`Header method failed (${response.status}): ${errorText}`);
412
+ }
413
  } catch (headerError) {
414
+ console.error('Header method error:', headerError);
415
+ validationErrors.push(`Header method failed: ${headerError.message}`);
416
+ }
417
+
418
+ // Method 2: URL parameter authentication if header failed
419
+ if (!validationSuccess) {
420
+ try {
421
+ console.log('Attempting validation with URL parameter...');
422
+ const testUrl = `https://texttospeech.googleapis.com/v1/voices?key=${encodeURIComponent(cleanApiKey)}&languageCode=en-US&pageSize=1`;
423
+ const response = await fetch(testUrl, {
424
+ method: 'GET',
425
+ headers: {
426
+ 'Accept': 'application/json',
427
+ },
428
+ mode: 'cors',
429
+ cache: 'no-cache'
430
+ });
431
+
432
+ if (response.ok) {
433
+ validationSuccess = true;
434
+ const data = await response.json();
435
+ console.log('URL parameter method successful:', data);
436
+ } else {
437
+ const errorText = await response.text();
438
+ validationErrors.push(`URL parameter method failed (${response.status}): ${errorText}`);
439
+ }
440
+ } catch (urlError) {
441
+ console.error('URL parameter method error:', urlError);
442
+ validationErrors.push(`URL parameter method failed: ${urlError.message}`);
443
+ }
444
  }
445
 
446
+ if (validationSuccess) {
447
+ apiKey = cleanApiKey;
448
+ localStorage.setItem('googleTTSApiKey', cleanApiKey);
449
  showToast('API key validated and saved successfully', 'success');
450
  loadVoices();
451
  } else {
452
+ // Show detailed validation errors
453
+ console.error('All validation methods failed:', validationErrors);
454
 
455
+ let errorMessage = 'API key validation failed. ';
456
+ if (validationErrors.some(err => err.includes('API_KEY_INVALID'))) {
457
+ errorMessage += 'The API key is invalid or not recognized by Google.';
458
+ } else if (validationErrors.some(err => err.includes('403'))) {
459
+ errorMessage += 'Access denied - check billing and API enablement.';
460
+ } else {
461
+ errorMessage += 'Please check the key format and permissions.';
462
  }
463
 
464
+ // Show comprehensive error details
465
+ const errorDetails = `
466
+ <div class="mt-3 text-xs text-gray-600 text-left">
467
+ <p class="font-medium mb-2">Validation attempts made:</p>
468
+ <ul class="list-disc list-inside space-y-1 mb-3">
469
+ ${validationErrors.map(err => `<li>${err}</li>`).join('')}
470
+ </ul>
471
+
472
+ <p class="font-medium mb-2">API Key Checklist:</p>
473
+ <ol class="list-decimal list-inside space-y-1">
474
+ <li>βœ“ Copy ONLY the API key (no extra text or spaces)</li>
475
+ <li>βœ“ Go to Google Cloud Console β†’ APIs & Services β†’ Credentials</li>
476
+ <li>βœ“ Ensure Text-to-Speech API is enabled</li>
477
+ <li>βœ“ Verify billing is enabled for your project</li>
478
+ <li>βœ“ Check API key restrictions (if any)</li>
479
+ <li>βœ“ Try creating a new unrestricted key for testing</li>
480
+ </ol>
481
+
482
+ <p class="mt-3 text-red-600 font-medium">Debug info: Key length: ${cleanApiKey.length}</p>
483
+ </div>
484
+ `;
485
 
486
+ // Create a modal or expandable section for the error
487
+ showDetailedError(errorMessage, errorDetails);
488
  }
489
  } catch (error) {
490
+ console.error('General API key validation error:', error);
491
+ showToast(`Validation error: ${error.message}`, 'error', 5000);
 
 
 
 
 
 
 
 
 
 
492
  } finally {
493
  // Reset button state
494
  saveKeyBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Save Key';
 
499
  }
500
  });
501
 
502
+ // Helper function to show detailed errors
503
+ function showDetailedError(message, details) {
504
+ const errorModal = document.createElement('div');
505
+ errorModal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
506
+ errorModal.innerHTML = `
507
+ <div class="bg-white rounded-lg p-6 max-w-2xl max-h-96 overflow-y-auto">
508
+ <div class="flex justify-between items-start mb-4">
509
+ <h3 class="text-lg font-semibold text-red-600">API Key Validation Failed</h3>
510
+ <button class="text-gray-400 hover:text-gray-600" onclick="this.parentElement.parentElement.parentElement.remove()">
511
+ <i class="fas fa-times text-xl"></i>
512
+ </button>
513
+ </div>
514
+ <p class="text-gray-700 mb-4">${message}</p>
515
+ ${details}
516
+ <div class="mt-6 flex justify-end">
517
+ <button class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded" onclick="this.parentElement.parentElement.parentElement.remove()">
518
+ Close
519
+ </button>
520
+ </div>
521
+ </div>
522
+ `;
523
+ document.body.appendChild(errorModal);
524
+ }
525
+
526
  // Load available voices from Google TTS
527
  async function loadVoices() {
528
  if (!apiKey) {