// OpenRouter Image Generator - 主要JavaScript逻辑 // 全局变量 let uploadedImages = []; let chatHistory = []; let generatedImages = []; let currentSettings = { apiKey: '', baseUrl: 'https://openrouter.ai/api/v1', model: 'google/gemini-2.5-flash-image-preview:free', timeout: 600, proxy: '' }; // 配置常量 const CONFIG = { MAX_FILE_SIZE: 10485760, // 10MB MAX_FILES_PER_UPLOAD: 1, // 限制为只上传一张图片 SUPPORTED_IMAGE_FORMATS: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], MAX_CHAT_HISTORY: 50, // 减少聊天历史记录数量以节省存储空间 MAX_GENERATED_IMAGES: 20, // 减少存储的图像数量以防止存储空间溢出 STORAGE_KEYS: { SETTINGS: 'openRouterSettings', CHAT_HISTORY: 'openRouterChatHistory', GENERATED_IMAGES: 'openRouterGeneratedImages' }, INDEXED_DB: { NAME: 'OpenRouterImageDB', VERSION: 1, STORES: { SETTINGS: 'settings', CHAT_HISTORY: 'chatHistory', GENERATED_IMAGES: 'generatedImages' } } }; // 错误消息 const ERROR_MESSAGES = { NO_API_KEY: '请先输入API Key', CONNECTION_FAILED: '连接失败,请检查网络和API设置', INVALID_RESPONSE: 'API响应格式错误', IMAGE_GENERATION_FAILED: '图像生成失败', FILE_TOO_LARGE: '文件大小超过限制', UNSUPPORTED_FORMAT: '不支持的文件格式', UPLOAD_FAILED: '文件上传失败' }; // 工具函数 const utils = { // 格式化文件大小 formatFileSize: function(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }, // 显示通知 showNotification: function(message, type = 'info') { const notification = document.createElement('div'); notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`; notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 300px;'; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); // 自动移除 setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); }, // 验证文件 validateFile: function(file) { if (file.size > CONFIG.MAX_FILE_SIZE) { throw new Error(ERROR_MESSAGES.FILE_TOO_LARGE); } if (!CONFIG.SUPPORTED_IMAGE_FORMATS.includes(file.type)) { throw new Error(ERROR_MESSAGES.UNSUPPORTED_FORMAT); } return true; }, // 防抖函数 debounce: function(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // 检查和清理存储空间 checkAndCleanStorage: async function() { if (!indexedDBStorage.isSupported) { console.warn('IndexedDB 不支持,无法进行存储检查'); return; } try { // 检查 IndexedDB 存储并清理过期数据 await indexedDBStorage.cleanupOldMessages(); await indexedDBStorage.cleanupOldImages(); console.log('IndexedDB 存储空间检查和清理完成'); } catch (error) { console.error('存储空间检查失败:', error); } }, // 检查存储状态 - 调试用 checkStorageStatus: async function() { console.log('=== 存储状态检查 ==='); console.log('IndexedDB 支持:', indexedDBStorage.isSupported); console.log('IndexedDB 数据库:', indexedDBStorage.db); if (indexedDBStorage.isSupported) { try { const settings = await indexedDBStorage.getSettings(); const chatHistory = await indexedDBStorage.getChatHistory(); const images = await indexedDBStorage.getGeneratedImages(); console.log('IndexedDB 数据统计:'); console.log(`- 设置: ${settings ? '已保存' : '未保存'}`); console.log(`- 聊天记录: ${chatHistory.length} 条`); console.log(`- 生成图像: ${images.length} 个`); chatHistory.forEach((msg, idx) => { console.log(` 聊天 ${idx}: ${msg.role} - ${msg.content.substring(0, 50)}...`); }); images.forEach((img, idx) => { console.log(` 图像 ${idx}: URL长度 ${img.url ? img.url.length : 0}`); }); } catch (error) { console.error('读取 IndexedDB 数据失败:', error); } } }, // 检查事件绑定 - 调试用 checkEventBindings: function() { console.log('=== 事件绑定检查 ==='); const containers = document.querySelectorAll('.image-clickable-container'); console.log(`找到 ${containers.length} 个可点击图像容器`); containers.forEach((container, idx) => { const img = container.querySelector('img'); const tooltip = container.querySelector('.image-preview-tooltip'); console.log(`容器 ${idx}:`, { src: img ? img.src.substring(0, 50) + '...' : '无图片', hasTooltip: !!tooltip, tooltipText: tooltip ? tooltip.textContent : '无', classList: container.classList.toString() }); }); // 测试点击第一个容器 if (containers.length > 0) { console.log('尝试点击第一个图像容器...'); containers[0].click(); } } }; // IndexedDB存储管理模块 const indexedDBStorage = { db: null, initPromise: null, isSupported: false, // 初始化数据库 init: function() { if (this.initPromise) { return this.initPromise; } this.initPromise = new Promise((resolve, reject) => { if (!window.indexedDB) { console.warn('IndexedDB 不支持,将使用 localStorage 作为后备'); this.db = null; this.isSupported = false; resolve(false); return; } const request = indexedDB.open(CONFIG.INDEXED_DB.NAME, CONFIG.INDEXED_DB.VERSION); request.onerror = (event) => { console.error('IndexedDB 初始化失败:', event.target.error); this.db = null; this.isSupported = false; resolve(false); }; request.onsuccess = (event) => { this.db = event.target.result; this.isSupported = true; console.log('IndexedDB 初始化成功'); resolve(true); }; request.onupgradeneeded = (event) => { const db = event.target.result; console.log('正在创建/升级数据库结构...'); // 创建设置存储 if (!db.objectStoreNames.contains(CONFIG.INDEXED_DB.STORES.SETTINGS)) { const settingsStore = db.createObjectStore(CONFIG.INDEXED_DB.STORES.SETTINGS, { keyPath: 'id' }); settingsStore.createIndex('timestamp', 'timestamp', { unique: false }); } // 创建聊天历史存储 if (!db.objectStoreNames.contains(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY)) { const chatStore = db.createObjectStore(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY, { keyPath: 'id', autoIncrement: true }); chatStore.createIndex('timestamp', 'timestamp', { unique: false }); chatStore.createIndex('role', 'role', { unique: false }); } // 创建图像记录存储 if (!db.objectStoreNames.contains(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES)) { const imagesStore = db.createObjectStore(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES, { keyPath: 'id', autoIncrement: true }); imagesStore.createIndex('timestamp', 'timestamp', { unique: false }); imagesStore.createIndex('url', 'url', { unique: false }); } }; }); return this.initPromise; }, // 保存设置 saveSettings: async function(settings) { if (!this.isSupported) { return false; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.SETTINGS], 'readwrite'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.SETTINGS); const data = { id: 'current', ...settings, timestamp: Date.now() }; await new Promise((resolve, reject) => { const request = store.put(data); request.onsuccess = () => resolve(true); request.onerror = () => reject(request.error); }); return true; } catch (error) { console.error('IndexedDB 保存设置失败:', error); return false; } }, // 获取设置 getSettings: async function() { if (!this.isSupported) { return null; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.SETTINGS], 'readonly'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.SETTINGS); return new Promise((resolve, reject) => { const request = store.get('current'); request.onsuccess = () => { const result = request.result; if (result) { // 移除 id 和 timestamp 字段 const { id, timestamp, ...settings } = result; resolve(settings); } else { resolve(null); } }; request.onerror = () => reject(request.error); }); } catch (error) { console.error('IndexedDB 获取设置失败:', error); return null; } }, // 添加聊天消息 addChatMessage: async function(role, content) { if (!this.isSupported) { return false; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.CHAT_HISTORY], 'readwrite'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY); const message = { role: role, content: content, timestamp: Date.now() }; await new Promise((resolve, reject) => { const request = store.add(message); request.onsuccess = () => resolve(true); request.onerror = () => reject(request.error); }); // 清理旧消息 await this.cleanupOldMessages(); return true; } catch (error) { console.error('IndexedDB 添加聊天消息失败:', error); return false; } }, // 获取聊天历史 getChatHistory: async function() { if (!this.isSupported) { return []; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.CHAT_HISTORY], 'readonly'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY); const index = store.index('timestamp'); return new Promise((resolve, reject) => { const request = index.getAll(); request.onsuccess = () => { const messages = request.result || []; // 按时间戳排序 messages.sort((a, b) => a.timestamp - b.timestamp); resolve(messages); }; request.onerror = () => reject(request.error); }); } catch (error) { console.error('IndexedDB 获取聊天历史失败:', error); return []; } }, // 添加生成的图像 addGeneratedImage: async function(imageId, imageUrl) { if (!this.isSupported) { return false; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES], 'readwrite'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES); const image = { imageId: imageId, url: imageUrl, timestamp: Date.now() }; await new Promise((resolve, reject) => { const request = store.add(image); request.onsuccess = () => resolve(true); request.onerror = () => reject(request.error); }); // 清理旧图像 await this.cleanupOldImages(); return true; } catch (error) { console.error('IndexedDB 添加图像记录失败:', error); return false; } }, // 获取生成的图像 getGeneratedImages: async function() { if (!this.isSupported) { return []; } try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES], 'readonly'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES); const index = store.index('timestamp'); return new Promise((resolve, reject) => { const request = index.getAll(); request.onsuccess = () => { const images = request.result || []; // 按时间戳排序 images.sort((a, b) => a.timestamp - b.timestamp); resolve(images.map(img => ({ id: img.imageId, url: img.url }))); }; request.onerror = () => reject(request.error); }); } catch (error) { console.error('IndexedDB 获取图像记录失败:', error); return []; } }, // 清理旧聊天消息 cleanupOldMessages: async function() { try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.CHAT_HISTORY], 'readwrite'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY); const countRequest = store.count(); countRequest.onsuccess = () => { const count = countRequest.result; if (count > CONFIG.MAX_CHAT_HISTORY) { const index = store.index('timestamp'); const request = index.openCursor(); let deleteCount = count - CONFIG.MAX_CHAT_HISTORY; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor && deleteCount > 0) { cursor.delete(); deleteCount--; cursor.continue(); } }; } }; } catch (error) { console.error('清理旧聊天消息失败:', error); } }, // 清理旧图像记录 cleanupOldImages: async function() { try { const transaction = this.db.transaction([CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES], 'readwrite'); const store = transaction.objectStore(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES); const countRequest = store.count(); countRequest.onsuccess = () => { const count = countRequest.result; if (count > CONFIG.MAX_GENERATED_IMAGES) { const index = store.index('timestamp'); const request = index.openCursor(); let deleteCount = count - CONFIG.MAX_GENERATED_IMAGES; request.onsuccess = (event) => { const cursor = event.target.result; if (cursor && deleteCount > 0) { cursor.delete(); deleteCount--; cursor.continue(); } }; } }; } catch (error) { console.error('清理旧图像记录失败:', error); } }, // 清除所有数据 clearAllData: async function() { if (!this.isSupported) { return false; } try { const transaction = this.db.transaction([ CONFIG.INDEXED_DB.STORES.SETTINGS, CONFIG.INDEXED_DB.STORES.CHAT_HISTORY, CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES ], 'readwrite'); const promises = [ new Promise((resolve) => { const request = transaction.objectStore(CONFIG.INDEXED_DB.STORES.SETTINGS).clear(); request.onsuccess = () => resolve(); }), new Promise((resolve) => { const request = transaction.objectStore(CONFIG.INDEXED_DB.STORES.CHAT_HISTORY).clear(); request.onsuccess = () => resolve(); }), new Promise((resolve) => { const request = transaction.objectStore(CONFIG.INDEXED_DB.STORES.GENERATED_IMAGES).clear(); request.onsuccess = () => resolve(); }) ]; await Promise.all(promises); return true; } catch (error) { console.error('IndexedDB 清除所有数据失败:', error); return false; } }, // 数据迁移:从localStorage到IndexedDB migrateFromLocalStorage: async function() { if (!this.isSupported) { return false; } try { console.log('开始从 localStorage 迁移数据到 IndexedDB...'); // 迁移设置 const savedSettings = localStorage.getItem(CONFIG.STORAGE_KEYS.SETTINGS); if (savedSettings) { const settings = JSON.parse(savedSettings); await this.saveSettings(settings); console.log('设置已迁移到 IndexedDB'); } // 迁移聊天历史 const savedChatHistory = localStorage.getItem(CONFIG.STORAGE_KEYS.CHAT_HISTORY); if (savedChatHistory) { const messages = JSON.parse(savedChatHistory); for (const msg of messages) { await this.addChatMessage(msg.role, msg.content); } console.log(`已迁移 ${messages.length} 条聊天记录到 IndexedDB`); } // 迁移图像记录 const savedImages = localStorage.getItem(CONFIG.STORAGE_KEYS.GENERATED_IMAGES); if (savedImages) { const images = JSON.parse(savedImages); for (const img of images) { await this.addGeneratedImage(img.id, img.url); } console.log(`已迁移 ${images.length} 条图像记录到 IndexedDB`); } console.log('数据迁移完成'); return true; } catch (error) { console.error('数据迁移失败:', error); return false; } } }; // API服务 const apiService = { // 测试连接 testConnection: async function(apiKey, baseUrl) { try { const response = await fetch(`${baseUrl}/models`, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }); if (response.ok) { return { success: true, data: await response.json() }; } else { return { success: false, error: response.statusText }; } } catch (error) { return { success: false, error: error.message }; } }, // 生成图像 generateImage: async function(message, images, settings) { const messages = [ { role: 'user', content: [ { type: 'text', text: message } ] } ]; // 添加上传的图像 images.forEach(img => { messages[0].content.push({ type: 'image_url', image_url: { url: img.data } }); }); const payload = { model: settings.model, messages: messages, stream: false }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), settings.timeout * 1000); try { const response = await fetch(`${settings.baseUrl}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${settings.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // 解析响应 const choice = data.choices[0]; const messageContent = choice.message.content; const images = []; // 提取图像数据 if (choice.message.images) { choice.message.images.forEach(img => { images.push(img.image_url.url); }); } return { success: true, content: messageContent, images: images, usage: data.usage }; } catch (error) { clearTimeout(timeoutId); throw error; } } }; // UI控制器 const uiController = { // 更新连接状态 updateConnectionStatus: function(connected) { const statusIndicator = document.getElementById('connectionStatus'); const connectionText = document.getElementById('connectionText'); if (connected) { statusIndicator.className = 'status-indicator status-connected'; connectionText.textContent = '已连接'; } else { statusIndicator.className = 'status-indicator status-disconnected'; connectionText.textContent = '未连接'; } }, // 显示加载状态 showLoading: function(show) { const loadingSpinner = document.getElementById('loadingSpinner'); loadingSpinner.style.display = show ? 'block' : 'none'; }, // 添加聊天消息 addChatMessage: async function(role, content) { const chatHistoryDiv = document.getElementById('chatHistory'); const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${role} fade-in`; const timestamp = new Date().toLocaleTimeString(); messageDiv.innerHTML = `
未选择图像
'; } else { preview.innerHTML = ''; uploadedImages.forEach(img => uiController.displayImagePreview(img)); } }, // 移除生成的图像 removeGeneratedImage: function(imageId) { uiController.removeGeneratedImage(imageId); }, // 下载图像 downloadImage: function(imageUrl) { const link = document.createElement('a'); link.href = imageUrl; link.download = `generated-image-${Date.now()}.png`; link.click(); }, // 复制图像URL copyImageUrl: function(imageUrl) { navigator.clipboard.writeText(imageUrl).then(() => { utils.showNotification('图像URL已复制到剪贴板', 'success'); }).catch(() => { utils.showNotification('复制失败,请手动复制', 'warning'); }); }, // 处理文件 handleFiles: function(files) { fileHandler.handleFiles(files); }, // 清除存储数据 clearStorage: async function() { if (confirm('确定要清除所有存储的数据吗?这将删除所有聊天记录和生成的图像记录。')) { try { if (indexedDBStorage.isSupported) { // 清除 IndexedDB 数据 const cleared = await indexedDBStorage.clearAllData(); if (cleared) { console.log('IndexedDB 数据已清除'); } else { console.error('IndexedDB 清除失败'); } } // 清空内存中的数据 chatHistory = []; generatedImages = []; uploadedImages = []; // 更新UI document.getElementById('chatHistory').innerHTML = ''; document.getElementById('imageGallery').innerHTML = ''; document.getElementById('imagePreview').innerHTML = ''; utils.showNotification('已成功清除所有存储数据', 'success'); } catch (error) { console.error('清除存储数据失败:', error); utils.showNotification('清除存储数据失败', 'danger'); } } } }; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', async () => { await app.init(); }); // 全局函数(供HTML调用) window.testConnection = () => app.testConnection(); window.sendMessage = () => app.sendMessage(); window.downloadImage = (url) => app.downloadImage(url); window.copyImageUrl = (url) => app.copyImageUrl(url); window.removeImage = (id) => app.removeImage(id); window.removeGeneratedImage = (id) => app.removeGeneratedImage(id); window.clearStorage = () => app.clearStorage(); // 调试函数 window.checkStorageStatus = () => utils.checkStorageStatus(); window.checkEventBindings = () => utils.checkEventBindings(); window.clearIndexedDB = async () => { if (indexedDBStorage.isSupported) { const cleared = await indexedDBStorage.clearAllData(); console.log('IndexedDB 已清空:', cleared); } };