feat: 在index.html和script.js中添加图像批量生成功能
This commit is contained in:
24
index.html
24
index.html
@@ -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
254
script.js
@@ -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);
|
||||
|
||||
119
styles.css
119
styles.css
@@ -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
138
test.html
@@ -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>
|
||||
Reference in New Issue
Block a user