// 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: '' }; let currentImageIndex = 0; // 当前查看的图像索引 let currentModalInstance = null; // 当前模态框实例 // 配置常量 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; } }, // 删除生成的图像记录 deleteGeneratedImage: async function(imageId) { 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); return new Promise((resolve, reject) => { // 遍历所有记录查找匹配的imageId const request = store.openCursor(); request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { const record = cursor.value; if (record.imageId === imageId) { // 找到匹配的记录,删除它 const deleteRequest = cursor.delete(); deleteRequest.onsuccess = () => resolve(true); deleteRequest.onerror = () => reject(deleteRequest.error); } else { // 继续查找下一条记录 cursor.continue(); } } else { // 遍历完成但没有找到记录 resolve(false); } }; request.onerror = () => reject(request.error); }); } catch (error) { console.error('IndexedDB 删除图像记录失败:', 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'; }, // 隐藏传统加载状态,用于批量生成模式 hideTraditionalLoading: function() { const loadingSpinner = document.getElementById('loadingSpinner'); loadingSpinner.style.display = '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(); const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // 为用户消息添加操作按钮 const actionButtons = role === 'user' ? `
` : ''; messageDiv.innerHTML = `
${role === 'user' ? '用户' : '助手'} ${timestamp}
${actionButtons}
${this.escapeHtml(content)}
`; chatHistoryDiv.appendChild(messageDiv); chatHistoryDiv.scrollTop = chatHistoryDiv.scrollHeight; // 添加到内存中的聊天历史 chatHistory.push({ role: role, content: content }); // 保存到存储 - 优先使用 IndexedDB await this.saveChatHistory(role, content); }, // 转义HTML escapeHtml: function(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, // 添加生成的图像 addGeneratedImage: async function(imageUrl) { const gallery = document.getElementById('imageGallery'); const imageDiv = document.createElement('div'); imageDiv.className = 'image-item fade-in'; const imageId = Date.now() + Math.random(); imageDiv.innerHTML = ` Generated Image
`; gallery.appendChild(imageDiv); // 添加事件监听器 const viewLargeBtn = imageDiv.querySelector('.view-large-btn'); const downloadBtn = imageDiv.querySelector('.download-btn'); const copyBtn = imageDiv.querySelector('.copy-url-btn'); const removeBtn = imageDiv.querySelector('.remove-btn'); if (viewLargeBtn) { viewLargeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.viewLargeImage(imageUrl); }); } if (downloadBtn) { downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); app.downloadImage(imageUrl); }); } if (copyBtn) { copyBtn.addEventListener('click', (e) => { e.stopPropagation(); app.copyImageUrl(imageUrl); }); } if (removeBtn) { removeBtn.addEventListener('click', async (e) => { e.stopPropagation(); await app.removeGeneratedImage(imageId); }); } // 添加到内存中的图像记录 generatedImages.push({ id: imageId, url: imageUrl }); // 显示"全部下载"按钮 this.updateDownloadAllButtonVisibility(); // 保存到存储 - 优先使用 IndexedDB await this.saveGeneratedImages(imageId, imageUrl); }, // 更新"全部下载"按钮的可见性 updateDownloadAllButtonVisibility: function() { const downloadAllBtn = document.getElementById('downloadAllImagesBtn'); if (downloadAllBtn) { downloadAllBtn.style.display = generatedImages.length > 0 ? 'block' : 'none'; } }, // 添加占位图像 addImagePlaceholder: function(index, total) { const gallery = document.getElementById('imageGallery'); const placeholderDiv = document.createElement('div'); placeholderDiv.className = 'image-placeholder fade-in'; placeholderDiv.id = `placeholder-${index}`; placeholderDiv.innerHTML = `
正在生成图像 ${index + 1}/${total}
`; gallery.appendChild(placeholderDiv); return placeholderDiv; }, // 替换占位图像为实际图像 replacePlaceholderWithImage: function(placeholderId, imageUrl) { const placeholder = document.getElementById(placeholderId); if (!placeholder) return; const imageId = Date.now() + Math.random(); placeholder.className = 'image-item fade-in'; placeholder.id = ''; placeholder.innerHTML = ` Generated Image
`; // 添加事件监听器 const viewLargeBtn = placeholder.querySelector('.view-large-btn'); const downloadBtn = placeholder.querySelector('.download-btn'); const copyBtn = placeholder.querySelector('.copy-url-btn'); const removeBtn = placeholder.querySelector('.remove-btn'); if (viewLargeBtn) { viewLargeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.viewLargeImage(imageUrl); }); } if (downloadBtn) { downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); app.downloadImage(imageUrl); }); } if (copyBtn) { copyBtn.addEventListener('click', (e) => { e.stopPropagation(); app.copyImageUrl(imageUrl); }); } if (removeBtn) { removeBtn.addEventListener('click', async (e) => { e.stopPropagation(); await app.removeGeneratedImage(imageId); }); } // 添加到内存中的图像记录 generatedImages.push({ id: imageId, url: imageUrl }); // 显示"全部下载"按钮 this.updateDownloadAllButtonVisibility(); // 保存到存储 - 优先使用 IndexedDB this.saveGeneratedImages(imageId, imageUrl); }, // 显示批量生成状态 showBatchStatus: function(message, show = true) { let statusDiv = document.getElementById('batchStatus'); if (!statusDiv) { statusDiv = document.createElement('div'); statusDiv.id = 'batchStatus'; statusDiv.className = 'batch-status'; document.body.appendChild(statusDiv); } statusDiv.textContent = message; if (show) { statusDiv.classList.add('show'); } else { statusDiv.classList.remove('show'); } }, // 新的查看大图功能 viewLargeImage: async function(imageUrl) { try { const modalElement = document.getElementById('imageViewerModal'); if (!modalElement) { utils.showNotification('无法打开图像查看:找不到模态框元素', 'danger'); return; } // 检查 Bootstrap 是否可用 if (typeof bootstrap === 'undefined') { utils.showNotification('无法打开图像查看:Bootstrap 未加载', 'danger'); return; } const modalImage = document.getElementById('viewerModalImage'); const downloadButton = document.getElementById('viewerDownloadImage'); const deleteButton = document.getElementById('viewerDeleteImage'); const prevButton = document.getElementById('prevImageBtn'); const nextButton = document.getElementById('nextImageBtn'); if (!modalImage) { utils.showNotification('无法打开图像查看:找不到图像元素', 'danger'); return; } // 找到当前图像在generatedImages中的索引 currentImageIndex = generatedImages.findIndex(img => img.url === imageUrl); // 设置图像源 modalImage.src = imageUrl; modalImage.onerror = function() { utils.showNotification('图像加载失败', 'danger'); }; // 设置下载按钮功能 if (downloadButton) { downloadButton.onclick = function() { app.downloadImage(imageUrl); }; } // 设置删除按钮功能 if (deleteButton) { deleteButton.onclick = function() { // 保存当前图像ID const currentImageId = generatedImages[currentImageIndex].id; // 删除当前图像 app.removeGeneratedImage(currentImageId).then(() => { // 检查删除后是否还有图像 if (generatedImages.length === 0) { // 没有图像了,关闭模态框 currentModalInstance.hide(); } else { // 更新当前图像索引 if (currentImageIndex >= generatedImages.length) { // 如果删除的是最后一张,显示上一张 currentImageIndex = generatedImages.length - 1; } // 如果索引仍然有效,显示图像 if (currentImageIndex >= 0 && currentImageIndex < generatedImages.length) { modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick = function() { const newCurrentImageId = generatedImages[currentImageIndex].id; app.removeGeneratedImage(newCurrentImageId).then(() => { // 再次检查删除后是否还有图像 if (generatedImages.length === 0) { currentModalInstance.hide(); } else { // 更新当前图像索引 if (currentImageIndex >= generatedImages.length) { currentImageIndex = generatedImages.length - 1; } // 如果索引仍然有效,显示图像 if (currentImageIndex >= 0 && currentImageIndex < generatedImages.length) { modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick = arguments.callee; } } }); }; } // 更新翻页按钮状态 uiController.updateNavigationButtons(); } }); }; } // 设置翻页按钮功能 if (prevButton) { prevButton.onclick = function() { if (currentImageIndex > 0) { currentImageIndex--; modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick =async function() { // 保存当前图像ID const currentImageId = generatedImages[currentImageIndex].id; // 删除当前图像 await app.removeGeneratedImage(currentImageId); // 检查删除后是否还有图像 if (generatedImages.length === 0) { // 没有图像了,关闭模态框 currentModalInstance.hide(); } else { // 更新当前图像索引 if (currentImageIndex >= generatedImages.length) { // 如果删除的是最后一张,显示上一张 currentImageIndex = generatedImages.length - 1; } // 如果索引仍然有效,显示图像 if (currentImageIndex >= 0 && currentImageIndex < generatedImages.length) { modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick = arguments.callee; } // 更新翻页按钮状态 uiController.updateNavigationButtons(); } }; } }; } if (nextButton) { nextButton.onclick = function() { if (currentImageIndex < generatedImages.length - 1) { currentImageIndex++; modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick = function() { // 保存当前图像ID const currentImageId = generatedImages[currentImageIndex].id; // 删除当前图像 app.removeGeneratedImage(currentImageId).then(() => { // 检查删除后是否还有图像 if (generatedImages.length === 0) { // 没有图像了,关闭模态框 currentModalInstance.hide(); } else { // 更新当前图像索引 if (currentImageIndex >= generatedImages.length) { // 如果删除的是最后一张,显示上一张 currentImageIndex = generatedImages.length - 1; } // 如果索引仍然有效,显示图像 if (currentImageIndex >= 0 && currentImageIndex < generatedImages.length) { modalImage.src = generatedImages[currentImageIndex].url; // 更新删除按钮对应的图像ID deleteButton.onclick = arguments.callee; } // 更新翻页按钮状态 uiController.updateNavigationButtons(); } }); }; } }; } // 更新翻页按钮状态 this.updateNavigationButtons(); // 创建并显示模态框 currentModalInstance = new bootstrap.Modal(modalElement); currentModalInstance.show(); } catch (error) { utils.showNotification(`无法打开图像查看: ${error.message}`, 'danger'); // 备用方案:在新窗口中打开图像 try { window.open(imageUrl, '_blank'); } catch (e) { // 静默处理错误 } } }, // 更新翻页按钮状态 updateNavigationButtons: function() { const prevButton = document.getElementById('prevImageBtn'); const nextButton = document.getElementById('nextImageBtn'); if (prevButton) { prevButton.style.display = currentImageIndex > 0 ? 'block' : 'none'; } if (nextButton) { nextButton.style.display = currentImageIndex < generatedImages.length - 1 ? 'block' : 'none'; } }, // 显示图像预览 displayImagePreview: function(imageData) { const preview = document.getElementById('imagePreview'); preview.innerHTML = `
${imageData.name}
${utils.formatFileSize(imageData.size)}
`; }, // 保存聊天历史 - 新增消息时调用 saveChatHistory: async function(role, content) { if (!indexedDBStorage.isSupported) { console.warn('IndexedDB 不支持,无法保存聊天历史'); return; } try { const saved = await indexedDBStorage.addChatMessage(role, content); if (!saved) { console.error('IndexedDB 保存聊天历史失败'); } } catch (error) { console.error('保存聊天历史失败:', error); } }, // 保存生成的图像 - 新增图像时调用 saveGeneratedImages: async function(imageId, imageUrl) { if (!indexedDBStorage.isSupported) { console.warn('IndexedDB 不支持,无法保存图像记录'); return; } try { const saved = await indexedDBStorage.addGeneratedImage(imageId, imageUrl); if (!saved) { console.error('IndexedDB 保存图像记录失败'); } } catch (error) { console.error('保存生成的图像失败:', error); } }, // 加载设置 loadSettings: async function() { if (!indexedDBStorage.isSupported) { console.warn('IndexedDB 不支持,无法加载设置'); return; } try { // 从IndexedDB加载设置 const indexedSettings = await indexedDBStorage.getSettings(); if (indexedSettings) { currentSettings = { ...currentSettings, ...indexedSettings }; } // 更新UI document.getElementById('apiKey').value = currentSettings.apiKey; document.getElementById('baseUrl').value = currentSettings.baseUrl; document.getElementById('model').value = currentSettings.model; document.getElementById('timeout').value = currentSettings.timeout; document.getElementById('proxy').value = currentSettings.proxy; // 加载聊天历史 const indexedChatHistory = await indexedDBStorage.getChatHistory(); if (indexedChatHistory && indexedChatHistory.length > 0) { chatHistory = indexedChatHistory; } // 重建聊天UI const self = this; chatHistory.forEach(msg => { const chatHistoryDiv = document.getElementById('chatHistory'); const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${msg.role} fade-in`; const timestamp = new Date(msg.timestamp || Date.now()).toLocaleTimeString(); const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // 为用户消息添加操作按钮 const actionButtons = msg.role === 'user' ? `
` : ''; messageDiv.innerHTML = `
${msg.role === 'user' ? '用户' : '助手'} ${timestamp}
${actionButtons}
${self.escapeHtml(msg.content)}
`; chatHistoryDiv.appendChild(messageDiv); }); // 加载生成的图像 const indexedImages = await indexedDBStorage.getGeneratedImages(); if (indexedImages && indexedImages.length > 0) { generatedImages = indexedImages; } // 重建图像UI generatedImages.forEach(img => { const gallery = document.getElementById('imageGallery'); const imageDiv = document.createElement('div'); imageDiv.className = 'image-item fade-in'; imageDiv.innerHTML = ` Generated Image
`; gallery.appendChild(imageDiv); // 添加事件监听器 const viewLargeBtn = imageDiv.querySelector('.view-large-btn'); const downloadBtn = imageDiv.querySelector('.download-btn'); const copyBtn = imageDiv.querySelector('.copy-url-btn'); const removeBtn = imageDiv.querySelector('.remove-btn'); if (viewLargeBtn) { viewLargeBtn.addEventListener('click', (e) => { e.stopPropagation(); uiController.viewLargeImage(img.url); }); } if (downloadBtn) { downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); app.downloadImage(img.url); }); } if (copyBtn) { copyBtn.addEventListener('click', (e) => { e.stopPropagation(); app.copyImageUrl(img.url); }); } if (removeBtn) { removeBtn.addEventListener('click', async (e) => { e.stopPropagation(); await app.removeGeneratedImage(img.id); }); } }); } catch (error) { console.error('加载设置失败:', error); } }, // 移除生成的图像 removeGeneratedImage: async function(imageId) { // 从内存中移除 generatedImages = generatedImages.filter(img => img.id !== imageId); // 从DOM中移除 const gallery = document.getElementById('imageGallery'); const imageElements = gallery.querySelectorAll('.image-item'); imageElements.forEach(element => { const removeBtn = element.querySelector('.remove-btn'); if (removeBtn && removeBtn.dataset.imageId == imageId) { element.remove(); } }); // 从IndexedDB中移除 if (indexedDBStorage.isSupported) { try { await indexedDBStorage.deleteGeneratedImage(imageId); } catch (error) { console.error('从IndexedDB删除图像记录失败:', error); } } // 更新"全部下载"按钮的可见性 this.updateDownloadAllButtonVisibility(); }, // 保存设置 saveSettings: async function() { if (!indexedDBStorage.isSupported) { console.warn('IndexedDB 不支持,无法保存设置'); return; } try { currentSettings.apiKey = document.getElementById('apiKey').value; currentSettings.baseUrl = document.getElementById('baseUrl').value; currentSettings.model = document.getElementById('model').value; currentSettings.timeout = parseInt(document.getElementById('timeout').value); currentSettings.proxy = document.getElementById('proxy').value; const saved = await indexedDBStorage.saveSettings(currentSettings); if (!saved) { console.error('IndexedDB 保存设置失败'); } } catch (error) { console.error('保存设置失败:', error); } } }; // 文件处理 const fileHandler = { // 处理拖拽 handleDragOver: function(e) { e.preventDefault(); e.currentTarget.classList.add('dragover'); }, handleDragLeave: function(e) { e.currentTarget.classList.remove('dragover'); }, handleDrop: function(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); const files = e.dataTransfer.files; app.handleFiles(files); }, // 处理文件选择 handleFileSelect: function(e) { const files = e.target.files; app.handleFiles(files); }, // 处理文件 handleFiles: function(files) { const preview = document.getElementById('imagePreview'); preview.innerHTML = ''; // 限制文件数量 const filesToProcess = Array.from(files).slice(0, CONFIG.MAX_FILES_PER_UPLOAD); filesToProcess.forEach(file => { try { utils.validateFile(file); if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function(e) { const imageData = { id: Date.now() + Math.random(), data: e.target.result, name: file.name, type: file.type, size: file.size }; uploadedImages.push(imageData); uiController.displayImagePreview(imageData); }; reader.readAsDataURL(file); } } catch (error) { utils.showNotification(`${file.name}: ${error.message}`, 'danger'); } }); } }; // 主应用对象 const app = { // 初始化 init: async function() { this.initializeEventListeners(); // 初始化 IndexedDB try { //console.log('正在初始化 IndexedDB...'); const indexedDBSupported = await indexedDBStorage.init(); if (indexedDBSupported) { //console.log('IndexedDB 初始化成功,将优先使用 IndexedDB 存储'); // 尝试迁移数据 await indexedDBStorage.migrateFromLocalStorage(); } else { console.warn('IndexedDB 不可用,将使用 localStorage 作为后备'); } } catch (error) { console.error('IndexedDB 初始化失败:', error); } // 启动时检查存储空间 await utils.checkAndCleanStorage(); await utils.checkStorageStatus(); await uiController.loadSettings(); // 初始化"全部下载"按钮的可见性 uiController.updateDownloadAllButtonVisibility(); console.log('OpenRouter Image Generator initialized'); }, // 初始化事件监听器 initializeEventListeners: function() { // 文件拖拽 const dropZone = document.getElementById('dropZone'); dropZone.addEventListener('dragover', fileHandler.handleDragOver); dropZone.addEventListener('dragleave', fileHandler.handleDragLeave); dropZone.addEventListener('drop', fileHandler.handleDrop); // 文件选择 document.getElementById('imageInput').addEventListener('change', fileHandler.handleFileSelect); // 回车发送消息 const messageInput = document.getElementById('messageInput'); const self = this; messageInput.addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); self.sendMessage(); } }); // 全部下载按钮事件 const downloadAllBtn = document.getElementById('downloadAllImagesBtn'); if (downloadAllBtn) { downloadAllBtn.addEventListener('click', function() { app.downloadAllImages(); }); } // 设置变化时自动保存 const inputs = ['apiKey', 'baseUrl', 'model', 'timeout', 'proxy']; inputs.forEach(id => { document.getElementById(id).addEventListener('change', utils.debounce(() => { uiController.saveSettings(); }, 1000)); }); // 页面卸载时保存设置 window.addEventListener('beforeunload', () => { uiController.saveSettings(); }); }, // 测试连接 testConnection: async function() { const apiKey = document.getElementById('apiKey').value; const baseUrl = document.getElementById('baseUrl').value; if (!apiKey) { utils.showNotification(ERROR_MESSAGES.NO_API_KEY, 'warning'); return; } uiController.showLoading(true); try { const result = await apiService.testConnection(apiKey, baseUrl); if (result.success) { uiController.updateConnectionStatus(true); utils.showNotification('连接成功!', 'success'); } else { uiController.updateConnectionStatus(false); utils.showNotification(`连接失败:${result.error}`, 'danger'); } } catch (error) { uiController.updateConnectionStatus(false); utils.showNotification(`连接失败:${error.message}`, 'danger'); } finally { uiController.showLoading(false); } }, // 发送消息 - 生成一张图像(使用批量生成方法) sendMessage: async function() { // 临时设置批量数量为1 const batchCountInput = document.getElementById('batchCount'); const originalBatchCount = batchCountInput.value; batchCountInput.value = 1; // 调用批量生成方法 await this.sendBatchMessage(); // 恢复原始批量数量 batchCountInput.value = originalBatchCount; }, // 批量发送消息 sendBatchMessage: async function() { const messageInput = document.getElementById('messageInput'); const batchCountInput = document.getElementById('batchCount'); const message = messageInput.value.trim(); const batchCount = parseInt(batchCountInput.value) || 6; if (!message) { utils.showNotification('请输入消息', 'warning'); return; } if (batchCount < 1 || batchCount > 20) { utils.showNotification('请输入有效的生成数量(1-20)', 'warning'); return; } const apiKey = document.getElementById('apiKey').value; if (!apiKey) { utils.showNotification(ERROR_MESSAGES.NO_API_KEY, 'warning'); return; } // 隐藏传统加载状态,使用占位图像代替 uiController.hideTraditionalLoading(); // 添加用户消息到聊天历史 uiController.addChatMessage('user', message); // 创建占位图像 const placeholders = []; for (let i = 0; i < batchCount; i++) { const placeholder = uiController.addImagePlaceholder(i, batchCount); placeholders.push(placeholder); } // 显示批量生成状态 uiController.showBatchStatus(`正在批量生成 ${batchCount} 张图像...`); let successCount = 0; let failCount = 0; let firstResponse = null; let completedCount = 0; // 同时发送所有请求,但每个请求完成后立即处理 const promises = []; for (let i = 0; i < batchCount; i++) { promises.push( this.generateImageWithRetry(message, uploadedImages, currentSettings, i, batchCount) .then(result => { if (result.success) { // 保存第一个响应用于添加聊天消息 if (!firstResponse) { firstResponse = result.response; uiController.addChatMessage('assistant', result.response.content); } // 显示生成的图像 if (result.response.images && result.response.images.length > 0) { // 如果返回多个图像,为每个图像创建新的占位符 if (result.response.images.length > 1) { // 移除原始占位符 const originalPlaceholder = document.getElementById(`placeholder-${i}`); if (originalPlaceholder) { originalPlaceholder.remove(); } // 为每个图像添加新的图像项 result.response.images.forEach((img, imgIndex) => { uiController.addGeneratedImage(img); }); } else { // 只有一个图像,直接替换占位符 uiController.replacePlaceholderWithImage(`placeholder-${i}`, result.response.images[0]); } successCount++; } else { // 没有图像数据,但请求成功,这种情况应该不会发生,因为generateImageWithRetry已经检查了 failCount++; } } else { // 生成失败,检查是否需要保留占位符 if (!result.keepPlaceholder) { // 移除占位符(旧的行为) const placeholder = document.getElementById(`placeholder-${i}`); if (placeholder) { placeholder.remove(); } } failCount++; } // 更新完成计数和状态 completedCount++; uiController.showBatchStatus(`已完成 ${completedCount}/${batchCount} 张图像...`); return { index: i, success: result.success }; }) ); } // 等待所有请求完成 await Promise.all(promises); // 检查存储空间 await utils.checkAndCleanStorage(); // 清空输入框 messageInput.value = ''; // 隐藏批量生成状态 setTimeout(() => { uiController.showBatchStatus('', false); }, 3000); // 显示最终结果 if (failCount === 0) { utils.showNotification(`批量生成完成!成功生成 ${successCount} 张图像`, 'success'); } else { utils.showNotification(`批量生成完成!成功 ${successCount} 张,失败 ${failCount} 张`, 'warning'); } }, // 带重试机制的图像生成方法 generateImageWithRetry: async function(message, images, settings, imageIndex, totalImages) { const maxRetries = 5; let retryCount = 0; while (retryCount <= maxRetries) { try { const response = await apiService.generateImage(message, images, settings); // 检查响应中是否包含图像 if (!response.images || response.images.length === 0) { throw new Error('生成响应中没有图像数据'); } return { success: true, response: response }; } catch (error) { retryCount++; console.error(`生成第 ${imageIndex + 1} 张图像失败,重试次数 ${retryCount}/${maxRetries}:`, error); if (retryCount <= maxRetries) { // 更新占位符状态,显示重试信息 const placeholder = document.getElementById(`placeholder-${imageIndex}`); if (placeholder) { const placeholderText = placeholder.querySelector('.image-placeholder-text'); if (placeholderText) { placeholderText.textContent = `正在重试生成图像 ${imageIndex + 1}/${totalImages} (${retryCount}/${maxRetries})`; } } // 等待一段时间后重试,使用指数退避策略 const delayTime = 1000 * Math.pow(2, retryCount - 1); await new Promise(resolve => setTimeout(resolve, delayTime)); } else { // 重试次数用完,返回失败,但不要移除占位符 // 更新占位符显示最终失败状态 const placeholder = document.getElementById(`placeholder-${imageIndex}`); if (placeholder) { const placeholderText = placeholder.querySelector('.image-placeholder-text'); if (placeholderText) { placeholderText.textContent = `生成图像 ${imageIndex + 1}/${totalImages} 失败,已重试 ${maxRetries} 次`; } const placeholderIcon = placeholder.querySelector('.image-placeholder-icon'); if (placeholderIcon) { placeholderIcon.className = 'fas fa-exclamation-triangle image-placeholder-icon'; placeholderIcon.style.color = '#dc3545'; } } return { success: false, error: error, keepPlaceholder: true }; } } } // 重试次数用完,返回失败,但不要移除占位符 return { success: false, error: new Error('重试次数用完'), keepPlaceholder: true }; }, // 移除上传的图像 removeImage: function(imageId) { uploadedImages = uploadedImages.filter(img => img.id !== imageId); const preview = document.getElementById('imagePreview'); if (uploadedImages.length === 0) { preview.innerHTML = '

未选择图像

'; } else { preview.innerHTML = ''; uploadedImages.forEach(img => uiController.displayImagePreview(img)); } }, // 移除生成的图像 removeGeneratedImage: async function(imageId) { await uiController.removeGeneratedImage(imageId); }, // 下载图像 downloadImage: function(imageUrl) { const link = document.createElement('a'); link.href = imageUrl; link.download = `generated-image-${Date.now()}.png`; link.click(); }, // 全部下载图像 downloadAllImages: function() { if (generatedImages.length === 0) { utils.showNotification('没有可下载的图像', 'warning'); return; } // 创建一个临时通知 utils.showNotification(`正在下载 ${generatedImages.length} 张图像...`, 'info'); // 逐个下载图像 generatedImages.forEach((img, index) => { setTimeout(() => { const link = document.createElement('a'); link.href = img.url; link.download = `generated-image-${Date.now()}-${index + 1}.png`; link.click(); // 最后一个图像下载完成后显示通知 if (index === generatedImages.length - 1) { setTimeout(() => { utils.showNotification(`已成功下载 ${generatedImages.length} 张图像`, 'success'); }, 500); } }, index * 200); // 每个图像下载间隔200ms,避免浏览器阻止多个下载 }); }, // 复制图像URL copyImageUrl: function(imageUrl) { navigator.clipboard.writeText(imageUrl).then(() => { utils.showNotification('图像URL已复制到剪贴板', 'success'); }).catch(() => { utils.showNotification('复制失败,请手动复制', 'warning'); }); }, // 处理文件 handleFiles: function(files) { fileHandler.handleFiles(files); }, // 复制消息内容到剪贴板 copyMessageContent: function(messageId) { const messageElement = document.getElementById(messageId); if (!messageElement) { utils.showNotification('找不到消息内容', 'danger'); return; } const content = messageElement.textContent; navigator.clipboard.writeText(content).then(() => { utils.showNotification('已复制到剪贴板', 'success'); }).catch(() => { utils.showNotification('复制失败,请手动复制', 'danger'); }); }, // 编辑消息内容,将其填入文本框 editMessageContent: function(messageId) { const messageElement = document.getElementById(messageId); if (!messageElement) { utils.showNotification('找不到消息内容', 'danger'); return; } const content = messageElement.textContent; const messageInput = document.getElementById('messageInput'); if (messageInput) { messageInput.value = content; messageInput.focus(); utils.showNotification('内容已填入文本框', 'success'); } else { utils.showNotification('找不到文本输入框', 'danger'); } }, // 清除存储数据 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.sendBatchMessage = () => app.sendBatchMessage(); window.downloadImage = (url) => app.downloadImage(url); window.downloadAllImages = () => app.downloadAllImages(); 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); } };