imagen / static /index.html
yaoqifa's picture
Update static/index.html
4c4706d verified
Raw History Blame Contribute Delete
16.5 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Imagen Generator</title>
<style>
:root {
--bg-color: #0b0f19;
--card-color: #1a1f2e;
--text-color: #e2e8f0;
--accent-color: #3b82f6;
--input-bg: #2d3748;
--danger-color: #ef4444;
--success-color: #10b981;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
}
.container {
width: 100%;
max-width: 1000px;
display: grid;
grid-template-columns: 350px 1fr;
gap: 20px;
}
.card {
background-color: var(--card-color);
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
h1 { margin-top: 0; font-size: 1.5rem; color: var(--accent-color); }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-size: 0.9rem; color: #a0aec0; }
input, select, textarea {
width: 100%;
padding: 10px;
background-color: var(--input-bg);
border: 1px solid #4a5568;
border-radius: 6px;
color: white;
box-sizing: border-box;
font-size: 0.95rem;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: var(--accent-color);
}
.file-upload-wrapper {
position: relative;
border: 2px dashed #4a5568;
border-radius: 6px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
}
.file-upload-wrapper:hover {
border-color: var(--accent-color);
}
#init_image_input {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
opacity: 0;
cursor: pointer;
}
.upload-placeholder { color: #a0aec0; pointer-events: none; }
.preview-container {
display: none;
margin-top: 10px;
position: relative;
flex-wrap: wrap;
gap: 12px;
}
/* 缩略图包裹层 */
.thumb-wrapper {
position: relative;
width: 80px;
height: 80px;
}
.thumb-img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 6px;
border: 1px solid #4a5568;
}
/* 单独删除按钮样式 */
.single-remove-btn {
position: absolute;
top: -8px;
right: -8px;
background: var(--danger-color);
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
line-height: 20px;
text-align: center;
padding: 0;
z-index: 11;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
/* 顶部一键清除按钮(可选保留) */
.clear-preview-btn {
background: #4a5568;
color: white;
border: none;
border-radius: 4px;
padding: 2px 8px;
cursor: pointer;
font-size: 0.75rem;
margin-bottom: 8px;
width: auto;
}
button {
width: 100%;
padding: 12px;
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s;
font-weight: bold;
opacity: 1;
}
button:hover:not(:disabled) {
background-color: #2563eb;
transform: translateY(-1px);
}
button:disabled {
background-color: #4a5568;
cursor: not-allowed;
opacity: 0.6;
}
.result-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
border: 2px dashed #4a5568;
border-radius: 12px;
position: relative;
margin-top: 20px;
}
#result-img {
max-width: 100%;
max-height: 600px;
border-radius: 8px;
display: none;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--accent-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
display: none;
}
.download-btn {
margin-top: 15px;
background-color: var(--success-color);
width: auto;
padding: 8px 20px;
display: none;
text-decoration: none;
color: white;
border-radius: 6px;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.disclaimer-box {
margin-bottom: 15px;
padding: 12px;
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 6px;
font-size: 0.85rem;
color: #cbd5e1;
line-height: 1.5;
}
.disclaimer-box strong { color: var(--danger-color); display: block; margin-bottom: 5px; }
.checkbox-wrapper {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 5px 0;
cursor: pointer;
}
.checkbox-wrapper input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
accent-color: var(--accent-color);
}
.checkbox-wrapper label {
display: inline;
margin: 0;
font-size: 0.95rem;
color: #e2e8f0;
cursor: pointer;
}
@media (max-width: 768px) {
.container { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<h1>Imagen Generator</h1>
<div class="form-group">
<label>API Key</label>
<input type="password" id="api_key" placeholder="sk-...">
</div>
<div class="form-group">
<label>模型名称</label>
<select id="model">
<option value="gpt-image-2">gpt-image-2(0.08)</option>
<option value="gemini-3-pro-image-preview">gemini-3-pro-image-preview(0.35)</option>
<option value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview(0.2)</option>
</select>
</div>
<div class="form-group">
<label>画面比例</label>
<select id="ratio">
<option value="16:9 (1080p/4K)">16:9 (1080p/4K 宽屏)</option>
<option value="1:1 (正方形)">1:1 (正方形)</option>
<option value="9:16 (手机壁纸)">9:16 (手机壁纸)</option>
<option value="4:3 (标准照片)">4:3 (标准照片)</option>
<option value="3:4 (竖向照片)">3:4 (竖向照片)</option>
<option value="21:9 (电影感)">21:9 (电影感)</option>
<option value="2:3 (艺术照)">2:3 (艺术照)</option>
</select>
</div>
<div class="form-group">
<label>清晰度等级</label>
<select id="scale">
<option value="1k">1k (标准)</option>
<option value="2k">2k (高清)</option>
<option value="4k">4k (超清 Pro)</option>
</select>
</div>
<div class="form-group">
<label>输出格式</label>
<select id="format">
<option value="png">PNG</option>
<option value="jpg">JPG</option>
</select>
</div>
</div>
<div class="card">
<div class="form-group">
<label>提示词 (Prompt)</label>
<textarea id="prompt" rows="4" placeholder="描述你想要的画面..."></textarea>
</div>
<div class="form-group">
<label>参考图 (支持多选,Chat模型可理解多图)</label>
<div class="file-upload-wrapper" id="upload-box">
<span class="upload-placeholder" id="upload-text">点击或拖拽上传图片 (支持多选)</span>
<input type="file" id="init_image_input" accept="image/jpeg, image/png" multiple>
</div>
<div class="preview-container" id="preview-container">
<div style="width: 100%; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.8rem; color: #a0aec0;">已选择的图片:</span>
<button class="clear-preview-btn" onclick="clearImagePreview()">全部清空</button>
</div>
</div>
</div>
<div class="disclaimer-box">
<strong>⚠️ 免责声明 (Disclaimer)</strong>
本工具仅供个人学习与技术研究使用。严禁生成色情、暴力、政治敏感等违法内容。用户生成的任何内容纯属个人行为,法律后果由用户自行承担。
</div>
<div class="checkbox-wrapper">
<input type="checkbox" id="agree_chk" onchange="toggleSubmitButton()">
<label for="agree_chk">我已知晓并承诺合规使用</label>
</div>
<button onclick="generateImage()" id="gen-btn" disabled>🚫 请先勾选免责声明</button>
<div class="result-area" id="result-area">
<div class="loader" id="loader"></div>
<img id="result-img" alt="Generated Image" />
<a id="download-link" class="download-btn" href="#" download="image.png">下载图片</a>
<p id="status-text" style="color: #a0aec0; margin-top: 10px;">等待生成...</p>
</div>
</div>
</div>
<script>
let initImagesList = [];
const fileInput = document.getElementById('init_image_input');
const previewContainer = document.getElementById('preview-container');
const uploadBox = document.getElementById('upload-box');
fileInput.addEventListener('change', async function(e) {
const files = Array.from(e.target.files);
if (!files.length) return;
previewContainer.style.display = 'flex';
uploadBox.style.display = 'none';
for (const file of files) {
if (file.size > 10 * 1024 * 1024) continue;
const base64Data = await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (ev) => resolve(ev.target.result);
reader.readAsDataURL(file);
});
if (base64Data) {
initImagesList.push(base64Data);
renderThumbnail(base64Data);
}
}
e.target.value = ''; // 重置以允许重新选择同一张图
});
// 渲染单个缩略图及其删除按钮
function renderThumbnail(data) {
const wrapper = document.createElement('div');
wrapper.className = 'thumb-wrapper';
const img = document.createElement('img');
img.className = 'thumb-img';
img.src = data;
const removeBtn = document.createElement('button');
removeBtn.className = 'single-remove-btn';
removeBtn.innerHTML = '×';
removeBtn.onclick = function() {
// 从数据列表中删除
const index = initImagesList.indexOf(data);
if (index > -1) initImagesList.splice(index, 1);
// 从界面删除
wrapper.remove();
// 如果删光了,恢复上传状态
if (initImagesList.length === 0) {
clearImagePreview();
}
};
wrapper.appendChild(img);
wrapper.appendChild(removeBtn);
previewContainer.appendChild(wrapper);
}
function clearImagePreview() {
fileInput.value = '';
initImagesList = [];
// 删除除了标题栏(第一个子节点)外的所有缩略图
const header = previewContainer.firstElementChild;
previewContainer.innerHTML = '';
previewContainer.appendChild(header);
previewContainer.style.display = 'none';
uploadBox.style.display = 'block';
}
function toggleSubmitButton() {
const checkbox = document.getElementById('agree_chk');
const btn = document.getElementById('gen-btn');
if (checkbox.checked) {
btn.disabled = false;
btn.innerText = "🚀 开始生成";
} else {
btn.disabled = true;
btn.innerText = "🚫 请先勾选免责声明";
}
}
async function generateImage() {
const apiKey = document.getElementById('api_key').value;
const prompt = document.getElementById('prompt').value;
const checkbox = document.getElementById('agree_chk');
if (!checkbox.checked) { alert("请先勾选免责声明!"); return; }
if (!apiKey) { alert("请输入 API Key"); return; }
if (!prompt) { alert("请输入提示词"); return; }
const btn = document.getElementById('gen-btn');
const loader = document.getElementById('loader');
const img = document.getElementById('result-img');
const downloadBtn = document.getElementById('download-link');
const statusText = document.getElementById('status-text');
btn.disabled = true;
btn.innerText = "生成中...";
loader.style.display = 'block';
img.style.display = 'none';
downloadBtn.style.display = 'none';
statusText.innerText = "正在请求 API...";
const payload = {
api_key: apiKey,
model: document.getElementById('model').value,
prompt: prompt,
ratio: document.getElementById('ratio').value,
scale: document.getElementById('scale').value,
format: document.getElementById('format').value,
init_images: initImagesList
};
try {
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
if (data.status === 'success') {
img.src = `data:image/${payload.format === 'jpg' ? 'jpeg' : 'png'};base64,${data.image_base64}`;
img.style.display = 'block';
downloadBtn.href = img.src;
downloadBtn.download = `generated_image.${payload.format}`;
downloadBtn.style.display = 'inline-block';
downloadBtn.innerText = "下载图片";
statusText.innerText = "";
} else {
statusText.innerText = "生成失败: " + data.message;
alert("错误: " + data.message);
}
} catch (error) {
console.error(error);
statusText.innerText = "网络请求失败";
} finally {
if (document.getElementById('agree_chk').checked) {
btn.disabled = false;
btn.innerText = "🚀 开始生成";
}
loader.style.display = 'none';
}
}
</script>
</body>
</html>