feat: 在index.html和script.js中添加图像批量生成功能

This commit is contained in:
2025-08-29 13:05:22 +08:00
parent 960210d961
commit 431e231730
4 changed files with 390 additions and 145 deletions

View File

@@ -98,14 +98,26 @@
</div>
</div>
<!-- 聊天输入区域 -->
<!-- 图像生成模块 -->
<div class="mb-4">
<h5><i class="fas fa-comments me-2"></i>对话输入</h5>
<div class="input-group">
<h5><i class="fas fa-image me-2"></i>图像生成</h5>
<div class="input-group mb-3">
<textarea class="form-control" id="messageInput" rows="3" placeholder="输入您的图像生成请求..."></textarea>
<button class="btn btn-primary" onclick="sendMessage()">
<i class="fas fa-paper-plane me-2"></i>发送
</button>
</div>
<div class="d-flex flex-column gap-3">
<div class="input-group mb-3">
<span class="input-group-text">生成数量</span>
<input type="number" class="form-control" id="batchCount" min="1" max="10" value="6" placeholder="输入生成数量">
</div>
<div class="d-flex gap-2">
<button class="btn btn-primary flex-fill" onclick="sendMessage()">
<i class="fas fa-paper-plane me-2"></i>生成一张
</button>
<button class="btn btn-success flex-fill" onclick="sendBatchMessage()">
<i class="fas fa-magic me-2"></i>批量生成
</button>
</div>
<small class="text-muted">支持1-10张图像同时生成</small>
</div>
</div>

254
script.js
View File

@@ -668,6 +668,12 @@ const uiController = {
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');
@@ -776,6 +782,119 @@ const uiController = {
await this.saveGeneratedImages(imageId, imageUrl);
},
// 添加占位图像
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 = `
<div class="image-placeholder-content">
<div class="image-placeholder-spinner"></div>
<i class="fas fa-image image-placeholder-icon"></i>
<div class="image-placeholder-text">
正在生成图像 ${index + 1}/${total}
</div>
</div>
`;
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 = `
<img src="${imageUrl}" alt="Generated Image" loading="lazy" class="generated-image">
<div class="image-overlay">
<div class="btn-grid">
<div class="btn-grid-row">
<button class="btn btn-sm btn-outline-light view-large-btn" data-image-url="${imageUrl}" title="查看大图">
<i class="fas fa-search-plus"></i>
</button>
<button class="btn btn-sm btn-outline-light download-btn" data-image-url="${imageUrl}" title="下载图像">
<i class="fas fa-download"></i>
</button>
</div>
<div class="btn-grid-row">
<button class="btn btn-sm btn-outline-light copy-url-btn" data-image-url="${imageUrl}" title="复制URL">
<i class="fas fa-copy"></i>
</button>
<button class="btn btn-sm btn-outline-light remove-btn" data-image-id="${imageId}" title="删除">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
`;
// 添加事件监听器
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', (e) => {
e.stopPropagation();
app.removeGeneratedImage(imageId);
});
}
// 添加到内存中的图像记录
generatedImages.push({ id: imageId, url: imageUrl });
// 保存到存储 - 优先使用 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: function(imageUrl) {
@@ -1199,7 +1318,7 @@ const app = {
}
},
// 发送消息
// 发送消息 - 生成一张图像
sendMessage: async function() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value.trim();
@@ -1257,6 +1376,138 @@ const app = {
}
},
// 批量发送消息
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 > 10) {
utils.showNotification('请输入有效的生成数量1-10', '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(
apiService.generateImage(message, uploadedImages, currentSettings)
.then(response => {
// 保存第一个响应用于添加聊天消息
if (!firstResponse) {
firstResponse = response;
uiController.addChatMessage('assistant', response.content);
}
// 显示生成的图像
if (response.images && response.images.length > 0) {
// 如果返回多个图像,为每个图像创建新的占位符
if (response.images.length > 1) {
// 移除原始占位符
const originalPlaceholder = document.getElementById(`placeholder-${i}`);
if (originalPlaceholder) {
originalPlaceholder.remove();
}
// 为每个图像添加新的图像项
response.images.forEach((img, imgIndex) => {
uiController.addGeneratedImage(img);
});
} else {
// 只有一个图像,直接替换占位符
uiController.replacePlaceholderWithImage(`placeholder-${i}`, response.images[0]);
}
successCount++;
} else {
// 移除占位符
const placeholder = document.getElementById(`placeholder-${i}`);
if (placeholder) {
placeholder.remove();
}
failCount++;
}
// 更新完成计数和状态
completedCount++;
uiController.showBatchStatus(`已完成 ${completedCount}/${batchCount} 张图像...`);
return { index: i, response };
})
.catch(error => {
console.error(`生成第 ${i + 1} 张图像失败:`, error);
// 移除占位符
const placeholder = document.getElementById(`placeholder-${i}`);
if (placeholder) {
placeholder.remove();
}
failCount++;
// 更新完成计数和状态
completedCount++;
uiController.showBatchStatus(`已完成 ${completedCount}/${batchCount} 张图像...`);
return { index: i, error };
})
);
}
// 等待所有请求完成
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');
}
},
// 移除上传的图像
removeImage: function(imageId) {
uploadedImages = uploadedImages.filter(img => img.id !== imageId);
@@ -1338,6 +1589,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// 全局函数供HTML调用
window.testConnection = () => app.testConnection();
window.sendMessage = () => app.sendMessage();
window.sendBatchMessage = () => app.sendBatchMessage();
window.downloadImage = (url) => app.downloadImage(url);
window.copyImageUrl = (url) => app.copyImageUrl(url);
window.removeImage = (id) => app.removeImage(id);

View File

@@ -460,3 +460,122 @@ body {
border-color: var(--success-color) !important;
background-color: rgba(40, 167, 69, 0.1) !important;
}
/* 占位图像样式 */
.image-placeholder {
position: relative;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: var(--transition);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px dashed #dee2e6;
}
.image-placeholder:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.image-placeholder-content {
width: 100%;
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #6c757d;
}
.image-placeholder-icon {
font-size: 3rem;
margin-bottom: 10px;
opacity: 0.5;
}
.image-placeholder-text {
font-size: 0.9rem;
text-align: center;
padding: 0 10px;
}
.image-placeholder-spinner {
position: absolute;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 批量生成状态指示器 */
.batch-status {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px 20px;
border-radius: 25px;
z-index: 9999;
display: none;
}
.batch-status.show {
display: block;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* 图像生成模块样式 */
.image-generation-module {
background: var(--light-color);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e9ecef;
}
.image-generation-module .input-group {
margin-bottom: 15px;
}
.image-generation-module .btn {
margin-bottom: 10px;
}
.image-generation-module .text-muted {
font-size: 0.85rem;
margin-top: 5px;
}
/* 确保按钮在移动设备上也能正确显示 */
@media (max-width: 768px) {
.image-generation-module .btn {
padding: 10px 15px;
font-size: 14px;
}
.image-generation-module .input-group-text {
font-size: 14px;
}
}

138
test.html
View File

@@ -1,138 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenRouter Image Generator - 测试页面</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2 class="text-center">OpenRouter Image Generator - 测试页面</h2>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
这是测试页面,用于验证应用是否正常工作。请检查浏览器控制台是否有错误信息。
</div>
<div class="mb-3">
<h5>功能测试清单:</h5>
<ul class="list-group">
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>
页面加载正常
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>
JavaScript文件加载成功
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>
事件监听器初始化完成
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>
UI控制器正常工作
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>
文件处理功能正常
</li>
</ul>
</div>
<div class="d-grid gap-2">
<a href="index.html" class="btn btn-primary">
<i class="fas fa-home me-2"></i>
返回主应用
</a>
<button class="btn btn-secondary" onclick="testFunctions()">
<i class="fas fa-flask me-2"></i>
测试核心功能
</button>
</div>
<div id="testResults" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function testFunctions() {
const results = document.getElementById('testResults');
results.innerHTML = '<div class="alert alert-info">正在测试功能...</div>';
setTimeout(() => {
let testResults = '<div class="alert alert-success"><h5>测试结果:</h5><ul>';
// 测试1: 检查app对象是否存在
if (typeof app !== 'undefined') {
testResults += '<li class="text-success">✓ app对象存在</li>';
} else {
testResults += '<li class="text-danger">✗ app对象不存在</li>';
}
// 测试2: 检查uiController是否存在
if (typeof uiController !== 'undefined') {
testResults += '<li class="text-success">✓ uiController存在</li>';
} else {
testResults += '<li class="text-danger">✗ uiController不存在</li>';
}
// 测试3: 检查apiService是否存在
if (typeof apiService !== 'undefined') {
testResults += '<li class="text-success">✓ apiService存在</li>';
} else {
testResults += '<li class="text-danger">✗ apiService不存在</li>';
}
// 测试4: 检查utils是否存在
if (typeof utils !== 'undefined') {
testResults += '<li class="text-success">✓ utils存在</li>';
} else {
testResults += '<li class="text-danger">✗ utils不存在</li>';
}
// 测试5: 检查fileHandler是否存在
if (typeof fileHandler !== 'undefined') {
testResults += '<li class="text-success">✓ fileHandler存在</li>';
} else {
testResults += '<li class="text-danger">✗ fileHandler不存在</li>';
}
// 测试6: 检查CONFIG是否存在
if (typeof CONFIG !== 'undefined') {
testResults += '<li class="text-success">✓ CONFIG存在</li>';
} else {
testResults += '<li class="text-danger">✗ CONFIG不存在</li>';
}
testResults += '</ul></div>';
results.innerHTML = testResults;
}, 1000);
}
// 页面加载完成后自动测试
document.addEventListener('DOMContentLoaded', function() {
console.log('测试页面加载完成');
console.log('检查全局对象:');
console.log('app:', typeof app);
console.log('uiController:', typeof uiController);
console.log('apiService:', typeof apiService);
console.log('utils:', typeof utils);
console.log('fileHandler:', typeof fileHandler);
console.log('CONFIG:', typeof CONFIG);
});
</script>
</body>
</html>