feat: implement API Key management features including save, clear, and test functionalities
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 精简版样式表
|
||||
* 注意:此站点现在主要使用 Tailwind CSS。
|
||||
* 这个文件只包含基本的重置样式,以支持可能不使用 Tailwind 的旧页面。
|
||||
*/
|
||||
|
||||
/* 基本样式重置 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 针对旧浏览器的代码高亮支持 */
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 确保音频播放器响应式适应 */
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,3 +1,29 @@
|
||||
function saveApiKey() {
|
||||
console.log('Save API Key function called directly');
|
||||
|
||||
const apiKeyInput = document.getElementById('api-key');
|
||||
const apiKeyGroup = document.getElementById('api-key-group');
|
||||
|
||||
if (apiKeyInput) {
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
if (apiKey) {
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('apiKey', apiKey);
|
||||
|
||||
// 显示成功消息
|
||||
showCustomAlert('API Key 已成功保存', 'success');
|
||||
|
||||
// 隐藏API Key输入区域
|
||||
if (apiKeyGroup) {
|
||||
apiKeyGroup.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
showCustomAlert('请输入有效的API Key', 'warning');
|
||||
apiKeyInput.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 获取DOM元素
|
||||
const textInput = document.getElementById('text');
|
||||
@@ -17,6 +43,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const charCount = document.getElementById('charCount');
|
||||
const toggleApiKeyButton = document.getElementById('toggle-api-key');
|
||||
const apiKeyStatus = document.getElementById('api-key-status');
|
||||
const apiKeySaveButton = document.getElementById('api-key-save');
|
||||
const apiKeyClearButton = document.getElementById('api-key-clear');
|
||||
const apiKeyTestButton = document.getElementById('api-key-test');
|
||||
const togglePasswordButton = document.getElementById('toggle-password');
|
||||
const apiKeyHelp = document.getElementById('api-key-help');
|
||||
|
||||
// 保存最后一个音频URL
|
||||
let lastAudioUrl = '';
|
||||
@@ -27,6 +60,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
initVoicesList();
|
||||
initEventListeners();
|
||||
loadApiKeyFromLocalStorage(); // 加载API Key
|
||||
updateApiKeyVisibility(); // 更新API Key区域的可见性
|
||||
|
||||
// 更新字符计数
|
||||
textInput.addEventListener('input', function() {
|
||||
@@ -61,6 +95,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 清空并重建选项
|
||||
voiceSelect.innerHTML = '';
|
||||
|
||||
// 如果还在加载,使用带有动画的加载提示
|
||||
if (voicesData.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.value = "loading";
|
||||
option.textContent = "加载中";
|
||||
option.className = "loading-text";
|
||||
voiceSelect.appendChild(option);
|
||||
return;
|
||||
}
|
||||
|
||||
// 按语言和名称分组
|
||||
const voicesByLocale = {};
|
||||
|
||||
@@ -96,7 +140,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
updateStyleOptions();
|
||||
} catch (error) {
|
||||
console.error('获取语音列表失败:', error);
|
||||
voiceSelect.innerHTML = '<option value="">无法加载语音列表</option>';
|
||||
voiceSelect.innerHTML = '<option value="" class="text-red-500">无法加载语音列表</option>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +208,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
copyHttpTtsLinkButton.addEventListener('click', function() {
|
||||
const text = textInput.value.trim();
|
||||
if (!text) {
|
||||
alert('请输入要转换的文本');
|
||||
showCustomAlert('请输入要转换的文本', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,121 +229,128 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
copyToClipboard(httpTtsLink);
|
||||
});
|
||||
|
||||
// 保存API Key按钮点击事件
|
||||
const saveApiKeyButton = document.createElement('button');
|
||||
saveApiKeyButton.textContent = '保存';
|
||||
saveApiKeyButton.className = 'flex-1 h-10 px-4 bg-gray-100 text-gray-700 border border-gray-300 rounded-md hover:bg-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50 flex items-center justify-center';
|
||||
saveApiKeyButton.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
|
||||
</svg>
|
||||
保存
|
||||
`;
|
||||
saveApiKeyButton.addEventListener('click', function() {
|
||||
saveApiKeyToLocalStorage(apiKeyInput.value);
|
||||
// 显示/隐藏API Key区域的按钮事件
|
||||
if (toggleApiKeyButton) {
|
||||
toggleApiKeyButton.addEventListener('click', function() {
|
||||
console.log('API Key button clicked'); // 添加调试日志
|
||||
if (apiKeyGroup) {
|
||||
apiKeyGroup.classList.toggle('hidden');
|
||||
|
||||
// 显示保存成功提示
|
||||
const successNotice = document.createElement('div');
|
||||
successNotice.className = 'fixed top-4 right-4 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded shadow-md flex items-center z-50';
|
||||
successNotice.innerHTML = `
|
||||
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
API Key 已保存
|
||||
`;
|
||||
document.body.appendChild(successNotice);
|
||||
// 如果是显示操作,聚焦到输入框
|
||||
if (!apiKeyGroup.classList.contains('hidden') && apiKeyInput) {
|
||||
apiKeyInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2秒后自动移除提示
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(successNotice);
|
||||
}, 2000);
|
||||
});
|
||||
apiKeyButtons.appendChild(saveApiKeyButton);
|
||||
// API Key显示/隐藏功能
|
||||
if (togglePasswordButton) {
|
||||
togglePasswordButton.addEventListener('click', function() {
|
||||
const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
apiKeyInput.setAttribute('type', type);
|
||||
|
||||
// 清除API Key按钮点击事件
|
||||
const clearApiKeyButton = document.createElement('button');
|
||||
clearApiKeyButton.textContent = '清除';
|
||||
clearApiKeyButton.className = 'flex-1 h-10 px-4 bg-gray-100 text-gray-700 border border-gray-300 rounded-md hover:bg-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50 flex items-center justify-center';
|
||||
clearApiKeyButton.innerHTML = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
清除
|
||||
`;
|
||||
clearApiKeyButton.addEventListener('click', function() {
|
||||
localStorage.removeItem('apiKey');
|
||||
apiKeyInput.value = '';
|
||||
// 更新图标
|
||||
if (type === 'password') {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>`;
|
||||
} else {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l18 18" />
|
||||
</svg>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示清除成功提示
|
||||
const successNotice = document.createElement('div');
|
||||
successNotice.className = 'fixed top-4 right-4 bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded shadow-md flex items-center z-50';
|
||||
successNotice.innerHTML = `
|
||||
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
API Key 已清除
|
||||
`;
|
||||
document.body.appendChild(successNotice);
|
||||
// 删除原有的保存API Key事件绑定,使用HTML onclick
|
||||
|
||||
// 2秒后自动移除提示
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(successNotice);
|
||||
}, 2000);
|
||||
});
|
||||
apiKeyButtons.appendChild(clearApiKeyButton);
|
||||
}
|
||||
// 按Enter键保存API Key
|
||||
if (apiKeyInput) {
|
||||
apiKeyInput.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
saveApiKey(); // 直接调用全局保存函数
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 增加密码显示/隐藏功能
|
||||
const togglePasswordButton = document.getElementById('toggle-password');
|
||||
if (togglePasswordButton) {
|
||||
togglePasswordButton.addEventListener('click', function() {
|
||||
const apiKeyInput = document.getElementById('api-key');
|
||||
const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
apiKeyInput.setAttribute('type', type);
|
||||
// 清除API Key
|
||||
if (apiKeyClearButton) {
|
||||
apiKeyClearButton.addEventListener('click', function() {
|
||||
apiKeyInput.value = '';
|
||||
localStorage.removeItem('apiKey');
|
||||
showNotification('API Key 已清除', 'success');
|
||||
|
||||
// 更新图标
|
||||
if (type === 'password') {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>`;
|
||||
} else {
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l18 18" />
|
||||
</svg>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 更新状态提示
|
||||
apiKeyStatus.textContent = '';
|
||||
apiKeyStatus.className = 'api-key-status hidden';
|
||||
});
|
||||
}
|
||||
|
||||
// 复制内容到剪贴板的通用函数
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('链接已复制到剪贴板');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
// 兼容处理
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
// 测试API Key
|
||||
if (apiKeyTestButton) {
|
||||
apiKeyTestButton.addEventListener('click', function() {
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
if (!apiKey) {
|
||||
showNotification('请先输入API Key再测试', 'warning');
|
||||
apiKeyInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('链接已复制到剪贴板');
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
}
|
||||
apiKeyTestButton.disabled = true;
|
||||
apiKeyTestButton.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> 测试中...';
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
// 使用一个简单请求测试API Key
|
||||
fetch(`${config.basePath}/voices?limit=1`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
apiKeyStatus.textContent = 'API Key 有效';
|
||||
apiKeyStatus.className = 'api-key-status valid';
|
||||
apiKeyStatus.classList.remove('hidden');
|
||||
showNotification('API Key 验证成功', 'success');
|
||||
} else {
|
||||
apiKeyStatus.textContent = 'API Key 无效';
|
||||
apiKeyStatus.className = 'api-key-status invalid';
|
||||
apiKeyStatus.classList.remove('hidden');
|
||||
showNotification('API Key 验证失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('测试API Key时出错:', error);
|
||||
apiKeyStatus.textContent = '测试失败,请检查网络';
|
||||
apiKeyStatus.className = 'api-key-status invalid';
|
||||
apiKeyStatus.classList.remove('hidden');
|
||||
showNotification('无法完成API Key测试', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
apiKeyTestButton.disabled = false;
|
||||
apiKeyTestButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> 测试';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 显示帮助信息
|
||||
if (apiKeyHelp) {
|
||||
apiKeyHelp.addEventListener('click', function() {
|
||||
showNotification('Azure API Key 可在Microsoft Azure门户中的认知服务资源下找到。更多信息请访问帮助文档。', 'info', 8000);
|
||||
});
|
||||
}
|
||||
|
||||
// 增强音频播放器
|
||||
enhanceAudioPlayer();
|
||||
}
|
||||
|
||||
// 生成语音
|
||||
async function generateSpeech() {
|
||||
const text = textInput.value.trim();
|
||||
if (!text) {
|
||||
alert('请输入要转换的文本');
|
||||
showCustomAlert('请输入要转换的文本', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -335,8 +386,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
if (response.status === 401) {
|
||||
// 显示API Key输入框
|
||||
apiKeyGroup.style.display = 'flex';
|
||||
alert('请输入有效的API Key以继续操作');
|
||||
apiKeyGroup.classList.remove('hidden');
|
||||
showCustomAlert('请输入有效的API Key以继续操作', 'error');
|
||||
throw new Error('需要API Key授权');
|
||||
}
|
||||
|
||||
@@ -360,7 +411,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
} catch (error) {
|
||||
console.error('生成语音失败:', error);
|
||||
if (error.message !== '需要API Key授权') {
|
||||
alert('生成语音失败,请重试');
|
||||
showCustomAlert('生成语音失败,请重试', 'error');
|
||||
}
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
@@ -371,18 +422,290 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// 保存API Key到localStorage
|
||||
function saveApiKeyToLocalStorage(apiKey) {
|
||||
console.log('Saving API Key to localStorage'); // 添加调试日志
|
||||
if (apiKey) {
|
||||
localStorage.setItem('apiKey', apiKey);
|
||||
} else {
|
||||
localStorage.removeItem('apiKey');
|
||||
// 如果清除了API Key,显示输入区域
|
||||
if (apiKeyGroup) {
|
||||
apiKeyGroup.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从localStorage加载API Key
|
||||
function loadApiKeyFromLocalStorage() {
|
||||
const apiKey = localStorage.getItem('apiKey');
|
||||
if (apiKey) {
|
||||
|
||||
if (apiKey && apiKeyInput) {
|
||||
apiKeyInput.value = apiKey;
|
||||
|
||||
// 显示已保存状态
|
||||
if (apiKeyStatus) {
|
||||
apiKeyStatus.textContent = 'API Key 已保存';
|
||||
apiKeyStatus.className = 'api-key-status valid';
|
||||
apiKeyStatus.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新API Key区域的可见性
|
||||
function updateApiKeyVisibility() {
|
||||
// 确保元素存在
|
||||
if (!apiKeyGroup) return;
|
||||
|
||||
// 如果有保存的API Key,则默认隐藏API Key输入区域
|
||||
const apiKey = localStorage.getItem('apiKey');
|
||||
if (apiKey && apiKey.trim() !== '') {
|
||||
apiKeyGroup.classList.add('hidden');
|
||||
} else {
|
||||
apiKeyGroup.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义音频播放器增强
|
||||
function enhanceAudioPlayer() {
|
||||
// 监听音频播放器出现在DOM中
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.addedNodes.length) {
|
||||
const audioPlayer = document.getElementById('audioPlayer');
|
||||
if (audioPlayer && !audioPlayer.dataset.enhanced) {
|
||||
// 标记为已增强,避免重复处理
|
||||
audioPlayer.dataset.enhanced = 'true';
|
||||
|
||||
// 为音频播放器添加额外样式
|
||||
audioPlayer.addEventListener('play', () => {
|
||||
audioPlayer.classList.add('playing');
|
||||
// 可以添加播放时的视觉反馈
|
||||
resultSection.classList.add('active-playback');
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('pause', () => {
|
||||
audioPlayer.classList.remove('playing');
|
||||
resultSection.classList.remove('active-playback');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 开始观察DOM变化
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// 复制内容到剪贴板的通用函数
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showCustomAlert('链接已复制到剪贴板', 'success');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
// 兼容处理
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showCustomAlert('链接已复制到剪贴板', 'success');
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加通知函数
|
||||
function showNotification(message, type = 'info', duration = 3000) {
|
||||
// 移除任何现有通知
|
||||
const existingNotifications = document.querySelectorAll('.api-key-notification');
|
||||
existingNotifications.forEach(notification => {
|
||||
document.body.removeChild(notification);
|
||||
});
|
||||
|
||||
// 创建通知元素
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `api-key-notification ${type}`;
|
||||
|
||||
// 添加图标
|
||||
let icon = '';
|
||||
switch(type) {
|
||||
case 'success':
|
||||
icon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#10b981"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>';
|
||||
break;
|
||||
case 'error':
|
||||
icon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#ef4444"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /></svg>';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#f59e0b"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /></svg>';
|
||||
break;
|
||||
default:
|
||||
icon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="#3b82f6"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" /></svg>';
|
||||
}
|
||||
|
||||
notification.innerHTML = `${icon}<span>${message}</span>`;
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示动画
|
||||
setTimeout(() => {
|
||||
notification.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// 设置自动关闭
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
document.body.removeChild(notification);
|
||||
}
|
||||
}, 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// 添加特定的初始化函数来确保API Key按钮功能正常
|
||||
function ensureApiKeyFunctionality() {
|
||||
const saveApiKeyBtn = document.getElementById('api-key-save');
|
||||
if (saveApiKeyBtn && !saveApiKeyBtn.hasEventListener) {
|
||||
saveApiKeyBtn.hasEventListener = true;
|
||||
saveApiKeyBtn.addEventListener('click', function() {
|
||||
console.log('Save API Key button clicked (fallback)');
|
||||
|
||||
const apiKeyInput = document.getElementById('api-key');
|
||||
if (apiKeyInput) {
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
if (apiKey) {
|
||||
localStorage.setItem('apiKey', apiKey);
|
||||
showCustomAlert('API Key 已成功保存', 'success');
|
||||
|
||||
const apiKeyGroup = document.getElementById('api-key-group');
|
||||
if (apiKeyGroup) {
|
||||
apiKeyGroup.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
showCustomAlert('请输入有效的API Key', 'warning');
|
||||
apiKeyInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 确保API Key功能在DOM加载后初始化
|
||||
setTimeout(ensureApiKeyFunctionality, 500);
|
||||
|
||||
// 添加自定义alert函数到全局范围
|
||||
window.showCustomAlert = showCustomAlert;
|
||||
});
|
||||
|
||||
// 自定义alert函数
|
||||
function showCustomAlert(message, type = 'info', title = '', duration = 3000) {
|
||||
// 获取或创建通知容器
|
||||
let container = document.getElementById('custom-alert-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'custom-alert-container';
|
||||
container.className = 'custom-alert-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// 创建通知元素
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `custom-alert ${type}`;
|
||||
|
||||
// 根据类型设置图标
|
||||
let iconSvg = '';
|
||||
switch (type) {
|
||||
case 'success':
|
||||
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-.997-6l7.07-7.071-1.414-1.414-5.656 5.657-2.829-2.829-1.414 1.414L11.003 16z"/></svg>';
|
||||
break;
|
||||
case 'error':
|
||||
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>';
|
||||
break;
|
||||
case 'warning':
|
||||
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>';
|
||||
break;
|
||||
default: // info
|
||||
iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z"/></svg>';
|
||||
}
|
||||
|
||||
// 构建通知内容
|
||||
alert.innerHTML = `
|
||||
<div class="custom-alert-icon">
|
||||
${iconSvg}
|
||||
</div>
|
||||
<div class="custom-alert-content">
|
||||
${title ? `<h4>${title}</h4>` : ''}
|
||||
<p class="custom-alert-message">${message}</p>
|
||||
</div>
|
||||
<button class="custom-alert-close" aria-label="关闭">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="custom-alert-progress"></div>
|
||||
`;
|
||||
|
||||
// 添加到容器
|
||||
container.appendChild(alert);
|
||||
|
||||
// 添加关闭事件
|
||||
const closeBtn = alert.querySelector('.custom-alert-close');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
removeAlert(alert);
|
||||
});
|
||||
|
||||
// 动画效果
|
||||
setTimeout(() => {
|
||||
alert.classList.add('show');
|
||||
|
||||
// 进度条动画
|
||||
const progress = alert.querySelector('.custom-alert-progress::after');
|
||||
if (progress) {
|
||||
progress.style.animation = `progress ${duration}ms linear forwards`;
|
||||
}
|
||||
}, 10);
|
||||
|
||||
// 自动关闭
|
||||
const timeout = setTimeout(() => {
|
||||
removeAlert(alert);
|
||||
}, duration);
|
||||
|
||||
// 清除函数
|
||||
function removeAlert(element) {
|
||||
element.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}, 300);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
// 返回alert对象,以便可以手动控制
|
||||
return {
|
||||
element: alert,
|
||||
close: () => removeAlert(alert)
|
||||
};
|
||||
}
|
||||
|
||||
// 替换全局的alert函数(可选,谨慎使用)
|
||||
const originalAlert = window.alert;
|
||||
window.alert = function(message) {
|
||||
showCustomAlert(message, 'info');
|
||||
};
|
||||
|
||||
// 在页面加载后,添加一次性检查以确保按钮可点击
|
||||
window.addEventListener('load', function() {
|
||||
console.log('Window loaded, checking save button...');
|
||||
const saveBtn = document.getElementById('api-key-save');
|
||||
if (saveBtn) {
|
||||
console.log('Save button found, adding fallback click handler');
|
||||
saveBtn.onclick = saveApiKey;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user