document.addEventListener('DOMContentLoaded', function() {
// 初始化配置
initConfig();
// 标签切换
const tabButtons = document.querySelectorAll('.tab-btn');
tabButtons.forEach(button => {
button.addEventListener('click', function(e) {
// 防止事件冒泡
e.stopPropagation();
const tabId = this.getAttribute('data-tab');
switchTab(tabId);
});
});
// 上传提供商切换
const uploadProviderSelect = document.getElementById('UPLOAD_PROVIDER');
if (uploadProviderSelect) {
uploadProviderSelect.addEventListener('change', function() {
toggleProviderConfig(this.value);
});
}
// 切换按钮事件
const toggleSwitches = document.querySelectorAll('.toggle-switch');
toggleSwitches.forEach(toggleSwitch => {
toggleSwitch.addEventListener('click', function(e) {
// 防止事件冒泡
e.stopPropagation();
const checkbox = this.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = !checkbox.checked;
}
});
});
// 保存按钮
const saveBtn = document.getElementById('saveBtn');
if (saveBtn) {
saveBtn.addEventListener('click', saveConfig);
}
// 重置按钮
const resetBtn = document.getElementById('resetBtn');
if (resetBtn) {
resetBtn.addEventListener('click', resetConfig);
}
// 滚动按钮
window.addEventListener('scroll', toggleScrollButtons);
// --- 新增:API Key 模态框和搜索相关 ---
const apiKeyModal = document.getElementById('apiKeyModal');
const addApiKeyBtn = document.getElementById('addApiKeyBtn');
const closeApiKeyModalBtn = document.getElementById('closeApiKeyModalBtn');
const cancelAddApiKeyBtn = document.getElementById('cancelAddApiKeyBtn');
const confirmAddApiKeyBtn = document.getElementById('confirmAddApiKeyBtn');
const apiKeyBulkInput = document.getElementById('apiKeyBulkInput');
const apiKeySearchInput = document.getElementById('apiKeySearchInput');
const bulkDeleteApiKeyBtn = document.getElementById('bulkDeleteApiKeyBtn'); // 新增
const bulkDeleteApiKeyModal = document.getElementById('bulkDeleteApiKeyModal'); // 新增
const closeBulkDeleteModalBtn = document.getElementById('closeBulkDeleteModalBtn'); // 新增
const cancelBulkDeleteApiKeyBtn = document.getElementById('cancelBulkDeleteApiKeyBtn'); // 新增
const confirmBulkDeleteApiKeyBtn = document.getElementById('confirmBulkDeleteApiKeyBtn'); // 新增
const bulkDeleteApiKeyInput = document.getElementById('bulkDeleteApiKeyInput'); // 新增
// --- 新增:重置确认模态框相关 ---
const resetConfirmModal = document.getElementById('resetConfirmModal');
const closeResetModalBtn = document.getElementById('closeResetModalBtn');
const cancelResetBtn = document.getElementById('cancelResetBtn');
const confirmResetBtn = document.getElementById('confirmResetBtn');
// --- 结束:新增 ---
// 打开模态框
if (addApiKeyBtn) {
addApiKeyBtn.addEventListener('click', () => {
if (apiKeyModal) {
apiKeyModal.classList.add('show');
}
if (apiKeyBulkInput) apiKeyBulkInput.value = ''; // 清空输入框
});
}
// 关闭模态框 (X 按钮)
if (closeApiKeyModalBtn) {
closeApiKeyModalBtn.addEventListener('click', () => {
if (apiKeyModal) {
apiKeyModal.classList.remove('show');
}
});
}
// 关闭模态框 (取消按钮)
if (cancelAddApiKeyBtn) {
cancelAddApiKeyBtn.addEventListener('click', () => {
if (apiKeyModal) {
apiKeyModal.classList.remove('show');
}
});
}
// 点击模态框外部关闭 (处理两个模态框)
window.addEventListener('click', (event) => {
if (event.target == apiKeyModal) {
apiKeyModal.classList.remove('show');
}
if (event.target == resetConfirmModal) {
resetConfirmModal.classList.remove('show');
}
if (event.target == bulkDeleteApiKeyModal) { // 新增对批量删除模态框的处理
bulkDeleteApiKeyModal.classList.remove('show');
}
});
// 确认添加 API Key
if (confirmAddApiKeyBtn) {
confirmAddApiKeyBtn.addEventListener('click', handleBulkAddApiKeys);
}
// API Key 搜索 (稍后实现具体逻辑)
if (apiKeySearchInput) {
apiKeySearchInput.addEventListener('input', handleApiKeySearch);
}
// --- 新增:批量删除 API Key 相关事件 ---
// 打开批量删除模态框
if (bulkDeleteApiKeyBtn) {
bulkDeleteApiKeyBtn.addEventListener('click', () => {
if (bulkDeleteApiKeyModal) {
bulkDeleteApiKeyModal.classList.add('show');
}
if (bulkDeleteApiKeyInput) bulkDeleteApiKeyInput.value = ''; // 清空输入框
});
}
// 关闭批量删除模态框 (X 按钮)
if (closeBulkDeleteModalBtn) {
closeBulkDeleteModalBtn.addEventListener('click', () => {
if (bulkDeleteApiKeyModal) {
bulkDeleteApiKeyModal.classList.remove('show');
}
});
}
// 关闭批量删除模态框 (取消按钮)
if (cancelBulkDeleteApiKeyBtn) {
cancelBulkDeleteApiKeyBtn.addEventListener('click', () => {
if (bulkDeleteApiKeyModal) {
bulkDeleteApiKeyModal.classList.remove('show');
}
});
}
// 确认批量删除 API Key
if (confirmBulkDeleteApiKeyBtn) {
confirmBulkDeleteApiKeyBtn.addEventListener('click', handleBulkDeleteApiKeys);
}
// --- 结束:批量删除 API Key 相关 ---
// --- 结束:API Key 相关 ---
// --- 新增:重置确认模态框事件监听 (移到 DOMContentLoaded 内部) ---
if (closeResetModalBtn) {
closeResetModalBtn.addEventListener('click', () => {
if (resetConfirmModal) {
resetConfirmModal.classList.remove('show');
}
});
}
if (cancelResetBtn) {
cancelResetBtn.addEventListener('click', () => {
if (resetConfirmModal) {
resetConfirmModal.classList.remove('show');
}
});
}
if (confirmResetBtn) {
// 调用之前定义的 executeReset 函数
confirmResetBtn.addEventListener('click', () => {
if (resetConfirmModal) {
resetConfirmModal.classList.remove('show'); // 关闭模态框
}
executeReset(); // 执行重置逻辑
});
}
// --- 结束:重置相关 ---
// 移除了静态生成令牌按钮的事件监听器,现在按钮是动态生成的
// 认证令牌生成按钮事件绑定
const generateAuthTokenBtn = document.getElementById('generateAuthTokenBtn');
const authTokenInput = document.getElementById('AUTH_TOKEN');
if (generateAuthTokenBtn && authTokenInput) {
generateAuthTokenBtn.addEventListener('click', function() {
const newToken = generateRandomToken();
authTokenInput.value = newToken;
showNotification('已生成新认证令牌', 'success');
});
}
}); // <-- DOMContentLoaded 结束括号
// 初始化配置
async function initConfig() {
try {
showNotification('正在加载配置...', 'info');
const response = await fetch('/api/config');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const config = await response.json();
// 确保数组字段有默认值
if (!config.API_KEYS || !Array.isArray(config.API_KEYS) || config.API_KEYS.length === 0) {
config.API_KEYS = ['请在此处输入 API 密钥'];
}
if (!config.ALLOWED_TOKENS || !Array.isArray(config.ALLOWED_TOKENS) || config.ALLOWED_TOKENS.length === 0) {
config.ALLOWED_TOKENS = [''];
}
if (!config.IMAGE_MODELS || !Array.isArray(config.IMAGE_MODELS) || config.IMAGE_MODELS.length === 0) {
config.IMAGE_MODELS = ['gemini-1.5-pro-latest'];
}
if (!config.SEARCH_MODELS || !Array.isArray(config.SEARCH_MODELS) || config.SEARCH_MODELS.length === 0) {
config.SEARCH_MODELS = ['gemini-1.5-flash-latest'];
}
if (!config.FILTERED_MODELS || !Array.isArray(config.FILTERED_MODELS) || config.FILTERED_MODELS.length === 0) {
config.FILTERED_MODELS = ['gemini-1.0-pro-latest'];
}
populateForm(config);
// 确保上传提供商有默认值
const uploadProvider = document.getElementById('UPLOAD_PROVIDER');
if (uploadProvider && !uploadProvider.value) {
uploadProvider.value = 'smms'; // 设置默认值为 smms
toggleProviderConfig('smms');
}
showNotification('配置加载成功', 'success');
} catch (error) {
console.error('加载配置失败:', error);
showNotification('加载配置失败: ' + error.message, 'error');
// 加载失败时,使用默认配置
const defaultConfig = {
API_KEYS: [''],
ALLOWED_TOKENS: [''],
IMAGE_MODELS: ['gemini-1.5-pro-latest'],
SEARCH_MODELS: ['gemini-1.5-flash-latest'],
FILTERED_MODELS: ['gemini-1.0-pro-latest'],
UPLOAD_PROVIDER: 'smms'
};
populateForm(defaultConfig);
toggleProviderConfig('smms');
}
}
// 填充表单
function populateForm(config) {
for (const [key, value] of Object.entries(config)) {
// 首先检查是否是数组类型
if (Array.isArray(value)) {
const container = document.getElementById(`${key}_container`);
if (container) {
// 清除现有项
const existingItems = container.querySelectorAll('.array-item');
existingItems.forEach(item => item.remove());
// 添加数组项
value.forEach(item => {
// 确保只添加非空字符串项(如果需要)
// if (item && typeof item === 'string' && item.trim() !== '') {
addArrayItemWithValue(key, item);
// }
});
}
// 处理完数组后,跳过本次循环的剩余部分
continue;
}
// 如果不是数组,再尝试查找对应的单个元素
const element = document.getElementById(key);
if (element) {
if (typeof value === 'boolean') {
element.checked = value;
} else {
// 处理其他类型 (确保 value 不是 null 或 undefined)
// 特别处理 LOG_LEVEL,确保大小写匹配 option 的 value
if (key === 'LOG_LEVEL' && typeof value === 'string') {
element.value = value.toUpperCase();
} else {
element.value = value ?? ''; // 使用空字符串作为默认值
}
}
}
// 如果既不是数组,也找不到对应 ID 的元素,则忽略该配置项
}
// 初始化上传提供商配置 (保持不变)
const uploadProvider = document.getElementById('UPLOAD_PROVIDER');
if (uploadProvider) {
toggleProviderConfig(uploadProvider.value);
}
}
// --- 新增:处理批量添加 API Key 的逻辑 ---
function handleBulkAddApiKeys() {
const apiKeyBulkInput = document.getElementById('apiKeyBulkInput');
const apiKeyContainer = document.getElementById('API_KEYS_container');
const apiKeyModal = document.getElementById('apiKeyModal');
if (!apiKeyBulkInput || !apiKeyContainer || !apiKeyModal) return;
const bulkText = apiKeyBulkInput.value;
const keyRegex = /AIzaSy\S{33}/g; // 全局匹配
const extractedKeys = bulkText.match(keyRegex) || [];
// 获取当前已有的 keys
const currentKeyInputs = apiKeyContainer.querySelectorAll('.array-input');
const currentKeys = Array.from(currentKeyInputs).map(input => input.value).filter(key => key.trim() !== '');
// 合并并去重
const combinedKeys = new Set([...currentKeys, ...extractedKeys]);
const uniqueKeys = Array.from(combinedKeys);
// 清空现有列表显示
const existingItems = apiKeyContainer.querySelectorAll('.array-item');
existingItems.forEach(item => item.remove());
// 重新填充列表
uniqueKeys.forEach(key => {
addArrayItemWithValue('API_KEYS', key);
});
// 关闭模态框
apiKeyModal.classList.remove('show');
showNotification(`添加/更新了 ${uniqueKeys.length} 个唯一密钥`, 'success');
}
// --- 新增:处理 API Key 搜索的逻辑 ---
function handleApiKeySearch() {
const apiKeySearchInput = document.getElementById('apiKeySearchInput');
const apiKeyContainer = document.getElementById('API_KEYS_container');
if (!apiKeySearchInput || !apiKeyContainer) return;
const searchTerm = apiKeySearchInput.value.toLowerCase();
const keyItems = apiKeyContainer.querySelectorAll('.array-item');
keyItems.forEach(item => {
const input = item.querySelector('.array-input');
if (input) {
const key = input.value.toLowerCase();
if (key.includes(searchTerm)) {
item.style.display = 'flex'; // 或者 'block',取决于你的布局
} else {
item.style.display = 'none';
}
}
});
}
// --- 新增:处理批量删除 API Key 的逻辑 ---
function handleBulkDeleteApiKeys() {
const bulkDeleteTextarea = document.getElementById('bulkDeleteApiKeyInput'); // Use the textarea ID
const apiKeyContainer = document.getElementById('API_KEYS_container');
const bulkDeleteModal = document.getElementById('bulkDeleteApiKeyModal');
if (!bulkDeleteTextarea || !apiKeyContainer || !bulkDeleteModal) return;
const bulkText = bulkDeleteTextarea.value;
if (!bulkText.trim()) {
showNotification('请粘贴需要删除的 API 密钥', 'warning');
return;
}
// Use the same regex as for adding keys to extract keys to delete
const keyRegex = /AIzaSy\S{33}/g;
const keysToDelete = new Set(bulkText.match(keyRegex) || []); // Create a Set for efficient lookup
if (keysToDelete.size === 0) {
showNotification('未在输入内容中提取到有效的 API 密钥格式', 'warning');
// Optionally clear the textarea or keep it as is
// bulkDeleteTextarea.value = '';
return;
}
const keyItems = apiKeyContainer.querySelectorAll('.array-item');
let deleteCount = 0;
keyItems.forEach(item => {
const input = item.querySelector('.array-input');
// Check if the input exists and its value is in the set of keys to delete
if (input && keysToDelete.has(input.value)) {
item.remove(); // Remove the entire array item element
deleteCount++;
}
});
// Close the modal
bulkDeleteModal.classList.remove('show');
// Provide feedback
if (deleteCount > 0) {
showNotification(`成功删除了 ${deleteCount} 个匹配的密钥`, 'success');
} else {
// This message implies keys were extracted but not found in the current list
showNotification('列表中未找到您输入的任何密钥进行删除', 'info');
}
// Clear the textarea after processing
bulkDeleteTextarea.value = '';
}
// 切换标签
function switchTab(tabId) {
// 更新标签按钮状态
const tabButtons = document.querySelectorAll('.tab-btn');
tabButtons.forEach(button => {
if (button.getAttribute('data-tab') === tabId) {
// 激活状态:主色背景,白色文字,添加阴影
button.classList.remove('bg-white', 'bg-opacity-50', 'text-gray-700', 'hover:bg-opacity-70');
button.classList.add('bg-primary-600', 'text-white', 'shadow-md');
} else {
// 非激活状态:白色背景,灰色文字,无阴影
button.classList.remove('bg-primary-600', 'text-white', 'shadow-md');
button.classList.add('bg-white', 'bg-opacity-50', 'text-gray-700', 'hover:bg-opacity-70');
}
});
// 更新内容区域
const sections = document.querySelectorAll('.config-section');
sections.forEach(section => {
if (section.id === `${tabId}-section`) {
section.classList.add('active');
} else {
section.classList.remove('active');
}
});
}
// 切换上传提供商配置
function toggleProviderConfig(provider) {
const providerConfigs = document.querySelectorAll('.provider-config');
providerConfigs.forEach(config => {
if (config.getAttribute('data-provider') === provider) {
config.classList.add('active');
} else {
config.classList.remove('active');
}
});
}
// 添加数组项
function addArrayItem(key) {
const container = document.getElementById(`${key}_container`);
if (!container) return;
addArrayItemWithValue(key, '');
}
// 添加带值的数组项
function addArrayItemWithValue(key, value) {
const container = document.getElementById(`${key}_container`);
if (!container) return;
const arrayItem = document.createElement('div');
// 主容器使用 Flexbox
arrayItem.className = 'array-item flex items-center mb-2 gap-2'; // 添加 gap-2 来分隔元素
// 创建一个包装器 div 来包含输入框和生成按钮
const inputWrapper = document.createElement('div');
// 这个包装器占据主要空间,并使用 Flexbox
inputWrapper.className = 'flex items-center flex-grow border border-gray-300 rounded-md focus-within:border-primary-500 focus-within:ring focus-within:ring-primary-200 focus-within:ring-opacity-50';
const input = document.createElement('input');
input.type = 'text';
input.name = `${key}[]`;
input.value = value;
// 输入框占据包装器内的主要空间,移除边框和圆角,因为包装器已有
input.className = 'array-input flex-grow px-3 py-2 border-none rounded-l-md focus:outline-none'; // 移除右侧圆角
inputWrapper.appendChild(input); // 将输入框添加到包装器
// 只为 ALLOWED_TOKENS 添加生成按钮
if (key === 'ALLOWED_TOKENS') {
const generateBtn = document.createElement('button');
generateBtn.type = 'button';
// 按钮样式,放在输入框右侧,有背景和内边距,调整颜色
generateBtn.className = 'generate-btn px-2 py-2 text-gray-500 hover:text-primary-600 focus:outline-none rounded-r-md bg-gray-100 hover:bg-gray-200 transition-colors'; // 添加背景和右侧圆角
generateBtn.innerHTML = '';
generateBtn.title = '生成随机令牌';
generateBtn.addEventListener('click', function() {
const newToken = generateRandomToken();
input.value = newToken;
showNotification('已生成新令牌', 'success');
});
inputWrapper.appendChild(generateBtn); // 将生成按钮添加到包装器
} else {
// 如果不是 ALLOWED_TOKENS,确保输入框有右侧圆角
input.classList.add('rounded-r-md');
}
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
// 删除按钮样式,保持不变
removeBtn.className = 'remove-btn text-gray-400 hover:text-red-500 focus:outline-none transition-colors duration-150';
removeBtn.innerHTML = '';
removeBtn.title = '删除';
removeBtn.addEventListener('click', function() {
arrayItem.remove();
});
// 将包装器(包含输入框和可能的生成按钮)和删除按钮添加到主容器
arrayItem.appendChild(inputWrapper);
arrayItem.appendChild(removeBtn);
// 插入到容器末尾
container.appendChild(arrayItem);
}
// 收集表单数据
function collectFormData() {
const formData = {};
// 处理普通输入
const inputs = document.querySelectorAll('input[type="text"], input[type="number"], select');
inputs.forEach(input => {
if (!input.name.includes('[]')) {
if (input.type === 'number') {
formData[input.name] = parseFloat(input.value);
} else {
// 确保 select 元素的值也被正确收集
formData[input.name] = input.value;
}
}
});
// 处理复选框
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
formData[checkbox.name] = checkbox.checked;
});
// 处理数组
const arrayContainers = document.querySelectorAll('.array-container');
arrayContainers.forEach(container => {
const key = container.id.replace('_container', '');
const arrayInputs = container.querySelectorAll('.array-input');
formData[key] = Array.from(arrayInputs).map(input => input.value).filter(value => value.trim() !== '');
});
return formData;
}
// 辅助函数:停止定时任务
async function stopScheduler() {
try {
const response = await fetch('/api/scheduler/stop', { method: 'POST' });
if (!response.ok) {
console.warn(`停止定时任务失败: ${response.status}`);
} else {
console.log('定时任务已停止');
}
} catch (error) {
console.error('调用停止定时任务API时出错:', error);
}
}
// 辅助函数:启动定时任务
async function startScheduler() {
try {
const response = await fetch('/api/scheduler/start', { method: 'POST' });
if (!response.ok) {
console.warn(`启动定时任务失败: ${response.status}`);
} else {
console.log('定时任务已启动');
}
} catch (error) {
console.error('调用启动定时任务API时出错:', error);
}
}
// 保存配置
async function saveConfig() {
try {
const formData = collectFormData();
showNotification('正在保存配置...', 'info');
// 1. 停止定时任务
await stopScheduler();
const response = await fetch('/api/config', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
// 移除居中的 saveStatus 提示
showNotification('配置保存成功', 'success');
// 3. 启动新的定时任务
await startScheduler();
} catch (error) {
console.error('保存配置失败:', error);
// 保存失败时,也尝试重启定时任务,以防万一
await startScheduler();
// 移除居中的 saveStatus 提示
showNotification('保存配置失败: ' + error.message, 'error');
}
}
// 重置配置 (现在只负责打开模态框)
function resetConfig(event) {
// 阻止事件冒泡和默认行为
if (event) {
event.preventDefault();
event.stopPropagation();
}
console.log('resetConfig called. Event target:', event ? event.target.id : 'No event');
// 确保只有当事件来自重置按钮时才显示模态框
if (!event || event.target.id === 'resetBtn' || event.currentTarget.id === 'resetBtn') {
const resetConfirmModal = document.getElementById('resetConfirmModal');
if (resetConfirmModal) {
resetConfirmModal.classList.add('show');
} else {
// Fallback if modal doesn't exist for some reason
console.error("Reset confirmation modal not found! Falling back to default confirm.");
// Fallback to original confirm behavior
if (!confirm('确定要重置所有配置吗?这将恢复到默认值。')) {
return;
}
// If confirmed, proceed with the reset logic directly (less ideal)
executeReset();
}
}
}
// --- 新增:将实际重置逻辑提取到一个单独的函数 ---
async function executeReset() {
try {
showNotification('正在重置配置...', 'info');
// 1. 停止定时任务
await stopScheduler();
const response = await fetch('/api/config/reset', { method: 'POST' });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const config = await response.json();
populateForm(config);
showNotification('配置已重置为默认值', 'success');
// 3. 启动新的定时任务
await startScheduler();
} catch (error) {
console.error('重置配置失败:', error);
showNotification('重置配置失败: ' + error.message, 'error');
// 重置失败时,也尝试重启定时任务
await startScheduler();
}
}
// 显示通知
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
// 统一样式为黑色半透明,与 keys_status.js 保持一致
notification.classList.remove('bg-danger-500');
notification.classList.add('bg-black');
notification.style.backgroundColor = 'rgba(0,0,0,0.8)';
notification.style.color = '#fff';
// 应用过渡效果
notification.style.opacity = "1";
notification.style.transform = "translate(-50%, 0)";
// 设置自动消失
setTimeout(() => {
notification.style.opacity = "0";
notification.style.transform = "translate(-50%, 10px)";
}, 3000);
}
// 刷新页面
function refreshPage(button) {
button.classList.add('loading');
location.reload();
}
// 滚动到顶部
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
// 滚动到底部
function scrollToBottom() {
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
});
}
// 切换滚动按钮显示
function toggleScrollButtons() {
const scrollButtons = document.querySelector('.scroll-buttons');
if (window.scrollY > 200) {
scrollButtons.style.display = 'flex';
} else {
scrollButtons.style.display = 'none';
}
}
// --- 新增:生成随机令牌函数 ---
function generateRandomToken() {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_';
const length = 48;
let result = 'sk-';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// --- 结束:生成随机令牌函数 ---