Files
hair/local_test/index.html
T
2026-07-16 22:57:12 +08:00

178 lines
7.2 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发型补全工具</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #1a1a2e; color: #eee; min-height: 100vh; padding: 20px; }
h1 { text-align: center; margin-bottom: 20px; color: #e94560; font-size: 28px; }
.container { max-width: 1400px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.panel { background: #16213e; border-radius: 12px; padding: 20px; }
.panel h2 { margin-bottom: 16px; font-size: 18px; color: #e94560; }
.controls { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px; align-items: center; }
.controls label { font-size: 14px; color: #aaa; }
input[type="file"] { color: #ddd; }
input[type="text"] { flex: 1; min-width: 200px; padding: 8px 12px; border-radius: 6px; border: 1px solid #444; background: #0f3460; color: #eee; font-size: 14px; }
button { padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s; }
.btn-upload { background: #0f3460; color: #eee; border: 1px solid #e94560; }
.btn-upload:hover { background: #1a4a7a; }
.btn-generate { background: #e94560; color: #fff; font-size: 16px; padding: 12px 36px; }
.btn-generate:hover { background: #c73650; }
.btn-generate:disabled { background: #555; cursor: not-allowed; }
.image-wrapper { display: flex; gap: 12px; flex-wrap: wrap; border: 2px dashed #444; border-radius: 8px; padding: 12px; min-height: 300px; background: #0f3460; }
.image-item { flex: 1; min-width: 200px; }
.image-item img { max-width: 100%; border-radius: 6px; }
.image-item h4 { font-size: 12px; color: #aaa; margin-bottom: 6px; }
.placeholder { color: #666; font-size: 16px; text-align: center; padding: 60px 20px; width: 100%; }
.result-area { display: flex; gap: 16px; flex-wrap: wrap; }
.result-area img { max-width: 100%; border-radius: 8px; }
.result-item { flex: 1; min-width: 250px; }
.result-item h3 { font-size: 14px; color: #aaa; margin-bottom: 8px; text-align: center; }
.loading { text-align: center; padding: 40px; color: #e94560; font-size: 18px; }
.loading .spinner { display: inline-block; width: 40px; height: 40px; border: 4px solid #333; border-top-color: #e94560; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 12px; }
@keyframes spin { to { transform: rotate(360deg); } }
.error { color: #ff6b6b; padding: 16px; background: #2a1a1a; border-radius: 8px; margin-top: 12px; }
</style>
</head>
<body>
<h1>💇 发型补全工具</h1>
<div class="container">
<!-- Left: Input -->
<div class="panel">
<h2>1. 上传图片 & 遮罩</h2>
<div class="controls">
<label>人物图片:</label>
<input type="file" id="imageInput" accept="image/*" class="btn-upload">
</div>
<div class="controls">
<label>遮罩图片:</label>
<input type="file" id="maskInput" accept="image/*" class="btn-upload">
</div>
<div class="image-wrapper" id="imageWrapper">
<div class="placeholder" id="placeholder">请上传人物图片和遮罩图片</div>
</div>
<div class="controls" style="margin-top:16px">
<label>提示词:</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮">
</div>
<div style="text-align:center; margin-top:16px">
<button class="btn-generate" id="generateBtn" disabled>🚀 生成</button>
</div>
</div>
<!-- Right: Result -->
<div class="panel">
<h2>2. 对比结果</h2>
<div id="resultArea">
<div class="placeholder">生成结果将显示在这里</div>
</div>
</div>
</div>
<script>
const imageInput = document.getElementById('imageInput');
const maskInput = document.getElementById('maskInput');
const imageWrapper = document.getElementById('imageWrapper');
const placeholder = document.getElementById('placeholder');
const promptInput = document.getElementById('promptInput');
const generateBtn = document.getElementById('generateBtn');
const resultArea = document.getElementById('resultArea');
let originalImage = null;
let maskImage = null;
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
const img = new Image();
img.onload = () => {
originalImage = { img: img, file: file };
updatePreview();
checkReady();
};
img.src = ev.target.result;
};
reader.readAsDataURL(file);
});
maskInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
const img = new Image();
img.onload = () => {
maskImage = { img: img, file: file };
updatePreview();
checkReady();
};
img.src = ev.target.result;
};
reader.readAsDataURL(file);
});
function updatePreview() {
placeholder.style.display = 'none';
let html = '';
if (originalImage) {
html += `<div class="image-item"><h4>人物图片</h4><img src="${originalImage.img.src}" alt="原图"></div>`;
}
if (maskImage) {
html += `<div class="image-item"><h4>遮罩图片</h4><img src="${maskImage.img.src}" alt="遮罩"></div>`;
}
imageWrapper.innerHTML = html;
}
function checkReady() {
generateBtn.disabled = !(originalImage && maskImage);
}
generateBtn.addEventListener('click', async () => {
if (!originalImage || !maskImage) return;
generateBtn.disabled = true;
generateBtn.textContent = '⏳ 生成中...';
resultArea.innerHTML = '<div class="loading"><div class="spinner"></div><br>正在调用 ComfyUI 生成,请耐心等待...</div>';
try {
const formData = new FormData();
formData.append('image', originalImage.file, 'original.' + originalImage.file.name.split('.').pop());
formData.append('mask', maskImage.file, 'mask.' + maskImage.file.name.split('.').pop());
formData.append('prompt', promptInput.value);
const resp = await fetch('/api/generate', { method: 'POST', body: formData });
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Generation failed');
}
const resultBlob = await resp.blob();
const resultUrl = URL.createObjectURL(resultBlob);
resultArea.innerHTML = `
<div class="result-area">
<div class="result-item">
<h3>原图</h3>
<img src="${originalImage.img.src}" alt="原图">
</div>
<div class="result-item">
<h3>生成结果</h3>
<img src="${resultUrl}" alt="生成结果">
</div>
</div>
`;
} catch (err) {
resultArea.innerHTML = `<div class="error">❌ ${err.message}</div>`;
} finally {
generateBtn.disabled = false;
generateBtn.textContent = '🚀 生成';
}
});
</script>
</body>
</html>