save code

This commit is contained in:
Ubuntu
2026-06-17 23:04:49 +08:00
parent 5311c8d7c7
commit c15a3c2e15
4 changed files with 75 additions and 28 deletions
+1 -1
View File
@@ -595,7 +595,7 @@ async def hair_grow_b(
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB"),
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
use_mask: bool = Form(default=True, description="是否启用自动检测遮罩(测试对比用)。false 时跳过检测、直接送划线图(空遮罩)"),
use_mask: bool = Form(default=True, description="是否画发际线(测试对比用)。false 时跳过划线检测、直接送划线图"),
):
# 划线图三选一取图(只需这一张)
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
+1 -1
View File
@@ -244,7 +244,7 @@
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| marked_image_* | file / string | 是 | 已用马克笔标注发际线的图片,三选一 |
| use_mask | bool | 否 | 是否启用自动检测遮罩,默认 `true``false` 时跳过划线检测、直接送划线图(空遮罩),模型仅凭手绘黑线生发,供测试对比 |
| use_mask | bool | 否 | 是否画发际线,默认 `true``false` 时跳过划线检测、直接送划线图,模型仅凭手绘黑线生发,供测试对比 |
### 输出(data
+8
View File
@@ -73,6 +73,13 @@
<label>性别</label>
<select id="gender"><option value="female" selected>👩 Female</option><option value="male">👨 Male</option></select>
</div>
<div class="form-group">
<label>inpaint 遮罩</label>
<select id="useMask">
<option value="true" selected>✅ 启用(默认)</option>
<option value="false">⬜ 关闭 — 空遮罩,不烧模板线</option>
</select>
</div>
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
</div>
@@ -118,6 +125,7 @@ async function submitTest() {
setStatus('⏳ 请求中(生发图生成较慢,请耐心)...', 'info'); $('resultsArea').classList.add('hidden');
const fd = new FormData(); fd.append('image_file', f); fd.append('gender', $('gender').value);
fd.append('use_mask', $('useMask').value);
try {
const r = await fetch(API_BASE + '/api/v1/hair/grow', { method:'POST', body:fd });
const json = await r.json();
+65 -26
View File
@@ -52,6 +52,17 @@
.result-field .name { font-size: 12px; font-weight: 600; color: #6b7280; margin-bottom: 4px; text-transform: uppercase; letter-spacing: .5px; }
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 12px; font-weight: 600; background: #dbeafe; color: #1e40af; }
/* blend */
.blend-container { position: relative; width: 100%; background: #222; border-radius: 8px; overflow: hidden; line-height: 0; }
.blend-container img { display: block; width: 100%; max-height: 500px; object-fit: contain; }
.blend-bottom { position: relative; z-index: 1; }
.blend-top { position: absolute; top: 0; left: 0; z-index: 2; transition: opacity 0.06s; }
.blend-placeholder { color: #9ca3af; font-size: 12px; padding: 80px 20px; background: #f9fafb; line-height: 1.5; }
.blend-controls { display: flex; align-items: center; gap: 10px; margin-top: 14px; justify-content: center; }
.blend-controls input[type=range] { width: 220px; accent-color: #2563eb; }
.blend-label { font-size: 12px; color: #6b7280; font-weight: 600; }
.blend-value { font-size: 13px; font-weight: 600; color: #374151; min-width: 42px; text-align: center; background: #f3f4f6; border-radius: 6px; padding: 2px 8px; }
.json-panel { max-height: 550px; overflow: auto; }
.json-content { padding: 14px 16px; font-family: "SF Mono", "Fira Code", monospace; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; }
@@ -72,6 +83,14 @@
<div class="label">✏️ 划线图(marked_image<span class="tag required">必填</span></div>
<input type="file" id="markedFile" accept="image/jpeg,image/png,.jpg,.jpeg,.png">
</div>
<div class="upload-group" style="margin-top:14px">
<div class="label">🪄 画发际线(use_mask<span class="tag-new">NEW</span></div>
<select id="useMask" style="padding:8px 12px;border:2px solid #ddd;border-radius:8px;font-size:14px;background:#fff;min-width:280px">
<option value="true" selected>✅ 画发际线(默认)— 检测划线并生成遮罩</option>
<option value="false">⬜ 不画发际线 — 跳过划线检测,直接送划线图(测试对比用)</option>
</select>
<div class="hint">false 时跳过划线检测、直接送划线图,用于对比测试</div>
</div>
<div style="margin-top:14px;display:flex;gap:12px;align-items:center">
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
@@ -83,24 +102,19 @@
<div class="results-layout hidden" id="resultsArea">
<div class="col-main">
<!-- 输入预览 -->
<div class="card">
<div class="card-header"><span>📸 划线图预览</span></div>
<div class="card-header"><span>🔄 原图 / 生发图 叠加对比 &nbsp;</span><span class="badge" id="hairlineType" style="display:none"></span></div>
<div class="card-body" style="text-align:center">
<div class="thumb" id="markedThumb"><span style="color:#9ca3af;padding:40px"></span></div>
<div class="blend-container" id="blendContainer">
<img id="blendBottom" class="blend-bottom" alt="原图(划线图)" style="display:none">
<img id="blendTop" class="blend-top" alt="生发效果图" style="display:none;opacity:0.5">
<div class="blend-placeholder" id="blendPlaceholder"></div>
</div>
</div>
<!-- 结果 -->
<div class="card">
<div class="card-header"><span>🎯 生发效果</span></div>
<div class="card-body">
<div class="result-field">
<div class="name">发际线类型</div>
<span class="badge" id="hairlineType"></span>
</div>
<div class="result-field">
<div class="name">生发效果图(hair_growth_image<span class="tag-new">ComfyUI</span></div>
<div class="thumb" id="growthImg"><span style="color:#9ca3af;padding:40px"></span></div>
<div class="blend-controls hidden" id="blendControls">
<span class="blend-label">原图</span>
<input type="range" id="blendSlider" min="0" max="1" step="0.01" value="0.5">
<span class="blend-label">生发图</span>
<span class="blend-value" id="blendValue">0.50</span>
</div>
</div>
</div>
@@ -125,14 +139,20 @@ async function submitTest() {
if (!mf) { setStatus('请选择划线图', 'error'); return; }
if (mf.size > 1_000_000) { setStatus('文件超过 1 MB', 'error'); return; }
// 预览划线图
$('markedThumb').innerHTML = '<img src="'+URL.createObjectURL(mf)+'" alt="划线图">';
const markedUrl = URL.createObjectURL(mf);
// 先显示原图
$('blendPlaceholder').style.display = 'none';
$('blendBottom').src = markedUrl;
$('blendBottom').style.display = 'block';
$('blendTop').style.display = 'none';
$('blendControls').classList.add('hidden');
$('submitBtn').disabled = true; $('submitBtn').textContent = '⏳ ...';
setStatus('请求中(生发需要数十秒)...', 'info'); $('resultsArea').classList.add('hidden');
setStatus('请求中(生发需要数十秒)...', 'info'); $('resultsArea').classList.remove('hidden');
const fd = new FormData();
fd.append('marked_image_file', mf);
fd.append('use_mask', $('useMask').value);
try {
const r = await fetch(API_BASE + '/api/v1/hair/grow-b', { method:'POST', body:fd });
@@ -142,18 +162,29 @@ async function submitTest() {
if (json.code === 0) {
const d = json.data;
$('hairlineType').textContent = d.hairline_type || '—';
if (d.hairline_type) {
$('hairlineType').textContent = '发际线: ' + d.hairline_type;
$('hairlineType').style.display = 'inline-block';
}
if (d.hair_growth_image_url) {
$('growthImg').innerHTML = '<img src="'+d.hair_growth_image_url+'" alt="生发效果">';
const grownSrc = d.hair_growth_image_url || d.hair_growth_image_base64;
if (grownSrc) {
$('blendTop').src = grownSrc;
$('blendTop').style.display = 'block';
$('blendTop').style.opacity = $('blendSlider').value;
$('blendControls').classList.remove('hidden');
$('blendValue').textContent = parseFloat($('blendSlider').value).toFixed(2);
} else {
$('growthImg').innerHTML = '<div class="na">⚠ 生发图未返回<br><span style="font-size:10px;color:#9ca3af">ComfyUI 未就绪或生成失败</span></div>';
$('blendTop').style.display = 'none';
$('blendControls').classList.add('hidden');
setStatus('⚠ 生发图未返回 — ComfyUI 未就绪或生成失败', 'info');
return;
}
setStatus('✅ 成功 &nbsp;|&nbsp; 发际线类型: ' + (d.hairline_type || '—'), 'success');
} else {
setStatus('❌ code=' + json.code + ' ' + json.message, 'error');
$('growthImg').innerHTML = '<div class="na">—</div>';
$('blendTop').style.display = 'none';
}
} catch(e) { setStatus('❌ ' + e.message, 'error'); $('resultsArea').classList.remove('hidden'); }
finally { $('submitBtn').disabled = false; $('submitBtn').textContent = '🚀 提交'; }
@@ -162,11 +193,19 @@ async function submitTest() {
function clearResults() {
$('resultsArea').classList.add('hidden'); $('statusBar').className='status hidden';
$('markedFile').value=''; $('jsonContent').textContent='';
$('markedThumb').innerHTML='<span style="color:#9ca3af;padding:40px">—</span>';
$('growthImg').innerHTML='<span style="color:#9ca3af;padding:40px">—</span>';
$('hairlineType').textContent='';
$('blendBottom').src = ''; $('blendBottom').style.display = 'none';
$('blendTop').src = ''; $('blendTop').style.display = 'none';
$('blendPlaceholder').style.display = 'block';
$('blendControls').classList.add('hidden');
$('blendSlider').value = 0.5; $('blendValue').textContent = '0.50';
$('hairlineType').textContent = ''; $('hairlineType').style.display = 'none';
}
$('blendSlider').addEventListener('input', function() {
$('blendTop').style.opacity = this.value;
$('blendValue').textContent = parseFloat(this.value).toFixed(2);
});
function copyJson() {
navigator.clipboard.writeText($('jsonContent').textContent).then(() => {
const b = event.target, o = b.textContent; b.textContent = '✅ 已复制'; setTimeout(() => b.textContent = o, 1500);