|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI图像工作室</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
min-height: 100vh; |
|
color: #333; |
|
} |
|
|
|
.container { |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
padding: 2rem; |
|
} |
|
|
|
.header { |
|
text-align: center; |
|
margin-bottom: 3rem; |
|
color: white; |
|
} |
|
|
|
.header h1 { |
|
font-size: 3rem; |
|
margin-bottom: 0.5rem; |
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3); |
|
} |
|
|
|
.header p { |
|
font-size: 1.2rem; |
|
opacity: 0.9; |
|
} |
|
|
|
.tabs { |
|
display: flex; |
|
justify-content: center; |
|
margin-bottom: 2rem; |
|
gap: 1rem; |
|
} |
|
|
|
.tab { |
|
padding: 1rem 2rem; |
|
background: rgba(255, 255, 255, 0.2); |
|
border: 2px solid rgba(255, 255, 255, 0.3); |
|
border-radius: 50px; |
|
color: white; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
font-weight: 600; |
|
backdrop-filter: blur(10px); |
|
} |
|
|
|
.tab:hover { |
|
background: rgba(255, 255, 255, 0.3); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.tab.active { |
|
background: white; |
|
color: #667eea; |
|
} |
|
|
|
.content { |
|
background: white; |
|
border-radius: 20px; |
|
padding: 2rem; |
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.form-group { |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
label { |
|
display: block; |
|
margin-bottom: 0.5rem; |
|
font-weight: 600; |
|
color: #555; |
|
} |
|
|
|
textarea { |
|
width: 100%; |
|
min-height: 120px; |
|
padding: 1rem; |
|
border: 2px solid #e0e0e0; |
|
border-radius: 10px; |
|
font-size: 1rem; |
|
transition: border-color 0.3s ease; |
|
resize: vertical; |
|
} |
|
|
|
textarea:focus { |
|
outline: none; |
|
border-color: #667eea; |
|
} |
|
|
|
select { |
|
width: 100%; |
|
padding: 0.75rem; |
|
border: 2px solid #e0e0e0; |
|
border-radius: 10px; |
|
font-size: 1rem; |
|
background: white; |
|
cursor: pointer; |
|
} |
|
|
|
.options-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
|
gap: 1rem; |
|
} |
|
|
|
.file-upload { |
|
position: relative; |
|
display: inline-block; |
|
cursor: pointer; |
|
width: 100%; |
|
} |
|
|
|
.file-upload input[type="file"] { |
|
position: absolute; |
|
opacity: 0; |
|
width: 100%; |
|
height: 100%; |
|
cursor: pointer; |
|
} |
|
|
|
.file-upload-label { |
|
display: block; |
|
padding: 2rem; |
|
border: 3px dashed #667eea; |
|
border-radius: 10px; |
|
text-align: center; |
|
background: #f8f9ff; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.file-upload:hover .file-upload-label { |
|
background: #eef0ff; |
|
border-color: #764ba2; |
|
} |
|
|
|
.generate-btn { |
|
width: 100%; |
|
padding: 1.25rem; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
border: none; |
|
border-radius: 10px; |
|
font-size: 1.1rem; |
|
font-weight: 600; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
margin-top: 1rem; |
|
} |
|
|
|
.generate-btn:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3); |
|
} |
|
|
|
.generate-btn:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
transform: none; |
|
} |
|
|
|
.result-section { |
|
margin-top: 3rem; |
|
text-align: center; |
|
} |
|
|
|
.result-image { |
|
max-width: 100%; |
|
border-radius: 10px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); |
|
margin-top: 1rem; |
|
} |
|
|
|
.loading { |
|
display: inline-block; |
|
width: 20px; |
|
height: 20px; |
|
border: 3px solid #f3f3f3; |
|
border-top: 3px solid #667eea; |
|
border-radius: 50%; |
|
animation: spin 1s linear infinite; |
|
margin-right: 10px; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
.error-message { |
|
background: #fee; |
|
color: #c33; |
|
padding: 1rem; |
|
border-radius: 10px; |
|
margin-top: 1rem; |
|
} |
|
|
|
.success-message { |
|
background: #efe; |
|
color: #3c3; |
|
padding: 1rem; |
|
border-radius: 10px; |
|
margin-top: 1rem; |
|
} |
|
|
|
.hidden { |
|
display: none; |
|
} |
|
|
|
.image-preview { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 1rem; |
|
margin-top: 1rem; |
|
} |
|
|
|
.preview-item { |
|
position: relative; |
|
width: 150px; |
|
height: 150px; |
|
border-radius: 10px; |
|
overflow: hidden; |
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.preview-item img { |
|
width: 100%; |
|
height: 100%; |
|
object-fit: cover; |
|
} |
|
|
|
.preview-item .remove { |
|
position: absolute; |
|
top: 5px; |
|
right: 5px; |
|
background: rgba(255, 255, 255, 0.9); |
|
color: #c33; |
|
border: none; |
|
border-radius: 50%; |
|
width: 30px; |
|
height: 30px; |
|
cursor: pointer; |
|
font-weight: bold; |
|
} |
|
|
|
.usage-info { |
|
margin-top: 1rem; |
|
padding: 1rem; |
|
background: #f0f4ff; |
|
border-radius: 10px; |
|
font-size: 0.9rem; |
|
color: #666; |
|
} |
|
|
|
.download-btn { |
|
margin-top: 1rem; |
|
padding: 0.75rem 1.5rem; |
|
background: #4CAF50; |
|
color: white; |
|
border: none; |
|
border-radius: 10px; |
|
font-size: 1rem; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.download-btn:hover { |
|
background: #45a049; |
|
transform: translateY(-2px); |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<header class="header"> |
|
<h1>🎨 AI 图像工作室</h1> |
|
<p>使用人工智能的力量创造惊艳图像</p> |
|
</header> |
|
|
|
<div class="tabs"> |
|
<div class="tab active" onclick="switchTab('generate')">生成图像</div> |
|
<div class="tab" onclick="switchTab('edit')">编辑图像</div> |
|
</div> |
|
|
|
<div class="content"> |
|
|
|
<div id="generate-tab" class="tab-content"> |
|
<form id="generate-form"> |
|
<div class="form-group"> |
|
<label for="prompt">描述您想要的图像</label> |
|
<textarea id="prompt" name="prompt" placeholder="夕阳下的宁静风景,山峦倒映在晶莹剔透的湖面上,水彩画风格..." required></textarea> |
|
</div> |
|
|
|
<div class="options-grid"> |
|
<div class="form-group"> |
|
<label for="size">图像尺寸</label> |
|
<select id="size" name="size"> |
|
<option value="1024x1024">正方形 (1024x1024)</option> |
|
<option value="1792x1024">横向 (1792x1024)</option> |
|
<option value="1024x1792">纵向 (1024x1792)</option> |
|
</select> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="quality">图像质量</label> |
|
<select id="quality" name="quality"> |
|
<option value="standard">标准</option> |
|
<option value="hd">高清 (费用更高)</option> |
|
</select> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="style">风格</label> |
|
<select id="style" name="style"> |
|
<option value="vivid">生动</option> |
|
<option value="natural">自然</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<button type="submit" class="generate-btn"> |
|
<span class="btn-text">生成图像</span> |
|
</button> |
|
</form> |
|
</div> |
|
|
|
|
|
<div id="edit-tab" class="tab-content hidden"> |
|
<form id="edit-form"> |
|
<div class="form-group"> |
|
<label>上传图像(仅支持1张)</label> |
|
<div class="file-upload"> |
|
<input type="file" id="image" name="image" accept="image/*" required> |
|
<label for="image" class="file-upload-label"> |
|
<div>📁 点击或拖拽图像到这里</div> |
|
<small>支持 PNG 格式(正方形,最大 4MB)</small> |
|
</label> |
|
</div> |
|
<div id="image-preview" class="image-preview"></div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label>上传蒙版(可选)</label> |
|
<div class="file-upload"> |
|
<input type="file" id="mask" name="mask" accept="image/png"> |
|
<label for="mask" class="file-upload-label"> |
|
<div>🎭 上传蒙版图像(可选)</div> |
|
<small>PNG 格式,透明区域表示要编辑的部分</small> |
|
</label> |
|
</div> |
|
<div id="mask-preview" class="image-preview"></div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="edit-prompt">描述您想要的编辑效果</label> |
|
<textarea id="edit-prompt" name="prompt" placeholder="在透明区域添加一只可爱的猫咪..." required></textarea> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="edit-size">输出尺寸</label> |
|
<select id="edit-size" name="size"> |
|
<option value="256x256">小尺寸 (256x256)</option> |
|
<option value="512x512">中尺寸 (512x512)</option> |
|
<option value="1024x1024">大尺寸 (1024x1024)</option> |
|
</select> |
|
</div> |
|
|
|
<button type="submit" class="generate-btn"> |
|
<span class="btn-text">编辑图像</span> |
|
</button> |
|
</form> |
|
</div> |
|
|
|
<div id="result-section" class="result-section hidden"> |
|
<h2>您生成的图像</h2> |
|
<img id="result-image" class="result-image" alt="生成的图像"> |
|
<div id="usage-info" class="usage-info hidden"></div> |
|
<button id="download-btn" class="download-btn" onclick="downloadImage()">下载图像</button> |
|
</div> |
|
|
|
<div id="message" class="hidden"></div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
let currentTab = 'generate'; |
|
let generatedImageData = null; |
|
let selectedImage = null; |
|
let selectedMask = null; |
|
|
|
function switchTab(tab) { |
|
currentTab = tab; |
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden')); |
|
|
|
event.target.classList.add('active'); |
|
document.getElementById(`${tab}-tab`).classList.remove('hidden'); |
|
|
|
|
|
document.getElementById('result-section').classList.add('hidden'); |
|
} |
|
|
|
|
|
document.getElementById('generate-form').addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
|
|
const btn = e.target.querySelector('.generate-btn'); |
|
const btnText = btn.querySelector('.btn-text'); |
|
|
|
|
|
btn.disabled = true; |
|
btnText.innerHTML = '<span class="loading"></span>生成中...'; |
|
|
|
|
|
showMessage(''); |
|
document.getElementById('result-section').classList.add('hidden'); |
|
|
|
const formData = { |
|
prompt: document.getElementById('prompt').value, |
|
size: document.getElementById('size').value, |
|
quality: document.getElementById('quality').value, |
|
style: document.getElementById('style').value |
|
}; |
|
|
|
try { |
|
const response = await fetch('/generate', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify(formData) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
generatedImageData = data.image; |
|
document.getElementById('result-image').src = data.image; |
|
document.getElementById('result-section').classList.remove('hidden'); |
|
|
|
if (data.usage) { |
|
const usageInfo = document.getElementById('usage-info'); |
|
usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`; |
|
usageInfo.classList.remove('hidden'); |
|
} |
|
|
|
showMessage('图像生成成功!', 'success'); |
|
} else { |
|
showMessage(data.error || '发生错误', 'error'); |
|
} |
|
} catch (error) { |
|
showMessage('网络错误: ' + error.message, 'error'); |
|
} finally { |
|
btn.disabled = false; |
|
btnText.innerHTML = '生成图像'; |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('edit-form').addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
|
|
const btn = e.target.querySelector('.generate-btn'); |
|
const btnText = btn.querySelector('.btn-text'); |
|
|
|
if (!selectedImage) { |
|
showMessage('请选择一张图像', 'error'); |
|
return; |
|
} |
|
|
|
|
|
btn.disabled = true; |
|
btnText.innerHTML = '<span class="loading"></span>处理中...'; |
|
|
|
|
|
showMessage(''); |
|
document.getElementById('result-section').classList.add('hidden'); |
|
|
|
const formData = new FormData(); |
|
formData.append('image', selectedImage); |
|
if (selectedMask) { |
|
formData.append('mask', selectedMask); |
|
} |
|
formData.append('prompt', document.getElementById('edit-prompt').value); |
|
formData.append('size', document.getElementById('edit-size').value); |
|
|
|
try { |
|
const response = await fetch('/edit', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
generatedImageData = data.image; |
|
document.getElementById('result-image').src = data.image; |
|
document.getElementById('result-section').classList.remove('hidden'); |
|
|
|
if (data.usage) { |
|
const usageInfo = document.getElementById('usage-info'); |
|
usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`; |
|
usageInfo.classList.remove('hidden'); |
|
} |
|
|
|
showMessage('图像编辑成功!', 'success'); |
|
} else { |
|
showMessage(data.error || '发生错误', 'error'); |
|
} |
|
} catch (error) { |
|
showMessage('网络错误: ' + error.message, 'error'); |
|
} finally { |
|
btn.disabled = false; |
|
btnText.innerHTML = '编辑图像'; |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('image').addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (file) { |
|
selectedImage = file; |
|
const preview = document.getElementById('image-preview'); |
|
preview.innerHTML = ''; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
const div = document.createElement('div'); |
|
div.className = 'preview-item'; |
|
div.innerHTML = ` |
|
<img src="${e.target.result}" alt="Preview"> |
|
<button class="remove" onclick="removeImage()">×</button> |
|
`; |
|
preview.appendChild(div); |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
}); |
|
|
|
|
|
document.getElementById('mask').addEventListener('change', (e) => { |
|
const file = e.target.files[0]; |
|
if (file) { |
|
selectedMask = file; |
|
const preview = document.getElementById('mask-preview'); |
|
preview.innerHTML = ''; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
const div = document.createElement('div'); |
|
div.className = 'preview-item'; |
|
div.innerHTML = ` |
|
<img src="${e.target.result}" alt="Mask Preview"> |
|
<button class="remove" onclick="removeMask()">×</button> |
|
`; |
|
preview.appendChild(div); |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
}); |
|
|
|
function removeImage() { |
|
selectedImage = null; |
|
document.getElementById('image').value = ''; |
|
document.getElementById('image-preview').innerHTML = ''; |
|
} |
|
|
|
function removeMask() { |
|
selectedMask = null; |
|
document.getElementById('mask').value = ''; |
|
document.getElementById('mask-preview').innerHTML = ''; |
|
} |
|
|
|
function showMessage(message, type = '') { |
|
const messageEl = document.getElementById('message'); |
|
if (!message) { |
|
messageEl.classList.add('hidden'); |
|
return; |
|
} |
|
|
|
messageEl.textContent = message; |
|
messageEl.className = type === 'error' ? 'error-message' : |
|
type === 'success' ? 'success-message' : ''; |
|
} |
|
|
|
function downloadImage() { |
|
if (!generatedImageData) return; |
|
|
|
const link = document.createElement('a'); |
|
link.href = generatedImageData; |
|
link.download = `ai-generated-${Date.now()}.png`; |
|
document.body.appendChild(link); |
|
link.click(); |
|
document.body.removeChild(link); |
|
} |
|
</script> |
|
</body> |
|
</html> |