- 接口11 移除重绘,仅生成 final;接口12 (grow_v2) 负责发际线带 Flux-2 重绘 - 接口12 重绘带改为发际线外推 band_lo_mult~band_hi_mult 倍 push(默认 0.5~1.5),页面可调 - 接口12 同时产出 A 整帧重绘 与 B 局部加发+全脸美颜(beauty_alpha 可调) - 新增 grow_v2_final(整帧重绘)/ grow_v2_final_v2(B 局部+美颜)端点:仅需图片+发型 ID,其余用固化默认值(color_match 关闭) - 配套精简测试页 test_interface12_final.html / test_interface12_final_v2.html / test_interface12.html - 恢复接口11 调试页多频段与换发型可调参数、color_match 默认不勾选 - 删除旧脚本 batch_grow_v2.py / gen_report_hairline_v2.py / test_simple.html Co-authored-by: Cursor <cursoragent@cursor.com>
362 lines
20 KiB
HTML
362 lines
20 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>接口12 — 发际线带重绘(Flux-2)测试页</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f5f5f5; color: #333; }
|
||
.container { max-width: 1240px; margin: 0 auto; padding: 24px; }
|
||
h1 { font-size: 22px; margin-bottom: 4px; }
|
||
h2 { font-size: 16px; margin: 20px 0 12px; }
|
||
.subtitle { color: #888; font-size: 13px; margin-bottom: 16px; line-height: 1.6; }
|
||
.card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 1px 4px rgba(0,0,0,.06); margin-bottom: 20px; }
|
||
.upload-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
||
input[type=file] { flex: 1; min-width: 200px; padding: 8px; border: 2px dashed #ddd; border-radius: 8px; cursor: pointer; }
|
||
.btn { padding: 10px 20px; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; font-weight: 600; }
|
||
.btn-primary { background: #7c3aed; color: #fff; }
|
||
.btn-primary:disabled { background: #c4b5fd; cursor: not-allowed; }
|
||
.btn-outline { background: #fff; border: 1px solid #d1d5db; color: #374151; }
|
||
.btn-green { background: #059669; color: #fff; }
|
||
.btn-orange { background: #ea580c; color: #fff; }
|
||
.params { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; margin-top: 16px; }
|
||
.pf { display: flex; flex-direction: column; gap: 4px; }
|
||
.pf label { font-size: 13px; font-weight: 600; }
|
||
.pf .desc { font-size: 11px; color: #9ca3af; font-weight: 400; }
|
||
.pf select, .pf input[type=number] { padding: 8px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; }
|
||
.pf .row { display: flex; gap: 8px; align-items: center; }
|
||
.pf .row input[type=number] { width: 80px; }
|
||
.status { padding: 10px 16px; border-radius: 8px; font-size: 14px; margin-bottom: 16px; display: none; font-family: monospace; }
|
||
.status.info { background: #ede9fe; color: #5b21b6; display: block; }
|
||
.status.error { background: #fee2e2; color: #991b1b; display: block; }
|
||
.status.success { background: #d1fae5; color: #065f46; display: block; }
|
||
.steps { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
||
.step { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
||
.step .cap { font-size: 13px; font-weight: 600; padding: 8px 12px; background: #fafafa; border-bottom: 1px solid #f0f0f0; }
|
||
.step .cap small { color: #999; font-weight: 400; display:block; margin-top:2px; }
|
||
.step img { width: 100%; display: block; background: #eee; cursor: zoom-in; }
|
||
.step .noimg { padding: 30px; text-align: center; color: #ccc; font-size: 13px; }
|
||
.big img { max-height: 520px; object-fit: contain; }
|
||
.log-panel { background: #1e1e1e; color: #d4d4d4; padding: 14px; border-radius: 8px;
|
||
font: 12px/1.6 Consolas, Monaco, monospace; white-space: pre-wrap; word-break: break-all;
|
||
max-height: 400px; overflow: auto; }
|
||
.log-panel .ts { color: #569cd6; }
|
||
.log-panel .lvl-info { color: #4ec9b0; }
|
||
.log-panel .lvl-warn { color: #dcdcaa; }
|
||
.log-panel .lvl-err { color: #f48771; }
|
||
.lightbox { position: fixed; inset: 0; background: rgba(0,0,0,.85); display: none; align-items: center; justify-content: center; z-index: 50; cursor: zoom-out; }
|
||
.lightbox img { max-width: 95%; max-height: 95%; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>接口12 — 发际线带重绘 <span style="font-size:13px;color:#888">(Flux-2 保色重绘)</span></h1>
|
||
<div class="subtitle">
|
||
内部先跑<b>接口11</b>拿到 ④接缝融合最终图(final),再取 <b>⑤-① 发际线重绘带</b>(发际线外推 band_lo_mult×push ~ band_hi_mult×push、经 baseline 截断只留上部)作遮罩,
|
||
调 <b>Flux-2(ComfyUI)</b> 保色重绘,重绘结果与 final 融合。<br>
|
||
接口11 参数用于内部生成 final 与重绘带;<b>comfyui_prompt</b> 控制 Flux-2 提示词。⚠️ 需 ComfyUI(:8188) 在跑。
|
||
对照仅生成不重绘:<a href="/static/test_interface11_debug.html">接口11 调试页</a>。
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="upload-row">
|
||
<input type="file" id="imageFile" accept="image/*">
|
||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🎨 提交重绘</button>
|
||
</div>
|
||
<div class="upload-row" style="margin-top:10px">
|
||
<label style="font-size:13px;font-weight:600;white-space:nowrap">X-Internal-Token</label>
|
||
<input type="text" id="token" value="dev-shared-secret-2026" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
|
||
</div>
|
||
<div class="params">
|
||
<div class="pf">
|
||
<label>发际线类型 ID</label>
|
||
<select id="hairlineId">
|
||
<option value="chang_bolang" selected>chang_bolang(波浪)</option>
|
||
<option value="chang_zhixian">chang_zhixian(直线)</option>
|
||
<option value="chang_tuoyuan">chang_tuoyuan(椭圆)</option>
|
||
<option value="chang_xinxing">chang_xinxing(心形)</option>
|
||
<option value="chang_huaban">chang_huaban(花瓣)</option>
|
||
</select>
|
||
</div>
|
||
<div class="pf">
|
||
<label>hairline_push_cm <span class="desc">发际线外推(cm),也决定重绘带宽度</span></label>
|
||
<div class="row"><input type="number" id="hairlinePushCm" min="0" max="3" step="0.1" value="0.8"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>mb_levels <span class="desc">多频段金字塔层数</span></label>
|
||
<div class="row"><input type="number" id="mbLevels" min="2" max="6" step="1" value="5"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>blend_method <span class="desc">接缝融合方法</span></label>
|
||
<select id="blendMethod">
|
||
<option value="two_stage" selected>two_stage(泊松→多频段,大色差)</option>
|
||
<option value="multiband">multiband(多频段金字塔)</option>
|
||
<option value="seamless">seamless(泊松无缝克隆)</option>
|
||
<option value="feather">feather(高斯羽化)</option>
|
||
<option value="alpha_gradient">alpha_gradient(距离变换)</option>
|
||
</select>
|
||
</div>
|
||
<div class="pf">
|
||
<label>color_match <span class="desc">融合前颜色迁移(消除色差)</span></label>
|
||
<div class="row"><input type="checkbox" id="colorMatch" checked style="width:18px;height:18px"><span class="desc">multiband/feather 生效;seamless/two_stage 自带调色</span></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>color_match_strength <span class="desc">颜色迁移强度(0~1)</span></label>
|
||
<div class="row"><input type="number" id="cmStrength" min="0" max="1" step="0.05" value="0.4"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>beauty_alpha <span class="desc">B版 band外美颜强度(0~1)</span></label>
|
||
<div class="row"><input type="number" id="beautyAlpha" min="0" max="1" step="0.05" value="0.6"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>band_lo_mult <span class="desc">重绘带外推倍率下限(×push)</span></label>
|
||
<div class="row"><input type="number" id="bandLoMult" min="0" max="3" step="0.1" value="0.5"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>band_hi_mult <span class="desc">重绘带外推倍率上限(×push)</span></label>
|
||
<div class="row"><input type="number" id="bandHiMult" min="0" max="3" step="0.1" value="1.5"></div>
|
||
</div>
|
||
</div>
|
||
<div class="pf" style="margin-top:14px">
|
||
<label>comfyui_prompt <span class="desc">Flux-2 提示词,留空=默认「补充遮罩区域的头发,加一点美颜」</span></label>
|
||
<textarea id="comfyuiPrompt" rows="2" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:8px;font-size:13px;font-family:inherit;resize:vertical" placeholder="留空用默认提示词"></textarea>
|
||
</div>
|
||
<div style="font-size:12px;color:#888;margin-top:8px">遮罩固定 pushed,重绘固定 Flux-2;隐藏参数(seg_model/hairline_edge/is_hr/edge_erode_px/mb_feather_px/transition_band_px/inpainting_fill/mask_blur/mask_dilate_scale)按原默认值随请求提交。换发型走 GPU + Flux-2 重绘,单次约 15~30s。</div>
|
||
</div>
|
||
|
||
<div class="status hidden" id="statusBar"></div>
|
||
|
||
<div id="resultsArea" class="hidden">
|
||
<div class="card">
|
||
<h2 style="margin-top:0">🎯 两版重绘对比</h2>
|
||
<div class="steps" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))">
|
||
<div class="step big"><div class="cap">接口11 final <small>④ 接缝融合(重绘输入基底,无美颜)</small></div><img id="finalBase"></div>
|
||
<div class="step big"><div class="cap">A · 整帧重绘 <small>全脸美颜 + 全脸重绘(=手动 ComfyUI)</small></div><img id="outFull"></div>
|
||
<div class="step big"><div class="cap">B · 局部加发+全脸美颜 <small>加发只在发际线带、美颜保留全脸</small></div><img id="outBand"></div>
|
||
</div>
|
||
<div style="font-size:12px;color:#888;margin-top:10px">对比要点:A 会重绘整张脸(五官/发型都可能变);B 只在发际线带加发、其余区域保留 final 结构并叠加全脸美颜(强度由 beauty_alpha 控制)。</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2 style="margin-top:0">🪜 分步可视化 <span style="font-size:12px;color:#888" id="stepsInfo"></span></h2>
|
||
<div class="steps" id="stepsGrid"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2 style="margin-top:0; display:flex; justify-content:space-between; align-items:center">
|
||
<span>📋 前端日志</span>
|
||
<div>
|
||
<button class="btn btn-green" style="padding:6px 14px;font-size:13px" onclick="downloadFrontendLog()">下载前端日志</button>
|
||
<button class="btn btn-orange" style="padding:6px 14px;font-size:13px" onclick="downloadBackendLog()">下载后端日志</button>
|
||
<button class="btn btn-outline" style="padding:6px 14px;font-size:13px" onclick="clearLog()">清空</button>
|
||
</div>
|
||
</h2>
|
||
<div class="log-panel" id="logPanel"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lightbox" id="lightbox" onclick="this.style.display='none'"><img id="lightboxImg" alt=""></div>
|
||
|
||
<script>
|
||
const API_BASE = window.location.origin;
|
||
const ENDPOINT = '/api/v1/hairline/grow_v2';
|
||
const LOG_API = '/api/v1/debug/hairline_log';
|
||
|
||
const FE_LOGS = [];
|
||
function flog(msg, level) {
|
||
const ts = new Date().toLocaleTimeString('zh-CN', {hour12:false}) + '.' + String(Date.now()%1000).padStart(3,'0');
|
||
const line = { ts, level: level||'info', msg };
|
||
FE_LOGS.push(line);
|
||
const panel = document.getElementById('logPanel');
|
||
const cls = level === 'warn' ? 'lvl-warn' : level === 'error' ? 'lvl-err' : 'lvl-info';
|
||
panel.innerHTML += '<span class="ts">[' + ts + ']</span> <span class="' + cls + '">' + line.msg.replace(/</g,'<') + '</span>\n';
|
||
panel.scrollTop = panel.scrollHeight;
|
||
}
|
||
function clearLog() { FE_LOGS.length = 0; document.getElementById('logPanel').innerHTML = ''; }
|
||
|
||
const STEPS = [
|
||
{ key: 'input', title: '原图', sub: '接口输入' },
|
||
{ key: 'final', title: '接口11 ④ final', sub: '接缝融合最终图(重绘输入基底,无美颜)' },
|
||
{ key: 'redraw_band_overlay', title: '⑤-① 发际线重绘带', sub: '紫=lo×push↔hi×push之间、经 baseline 截断只留上部' },
|
||
{ key: 'redraw_full', title: 'A · 整帧重绘(Flux-2)', sub: '全脸美颜+全脸重绘(=手动 ComfyUI)' },
|
||
{ key: 'redraw_band', title: 'B · 局部加发+全脸美颜', sub: 'band内=ComfyUI加发;band外=final+beauty_alpha美颜' },
|
||
];
|
||
|
||
function $(id) { return document.getElementById(id); }
|
||
function setStatus(text, type) { const b = $('statusBar'); b.textContent = text; b.className = 'status ' + type; }
|
||
function pick(obj, name) { if (!obj) return null; return obj[name + '_url'] || obj[name + '_base64'] || null; }
|
||
function zoom(src) { $('lightboxImg').src = src; $('lightbox').style.display = 'flex'; }
|
||
|
||
function stepCard(title, sub, src) {
|
||
const div = document.createElement('div');
|
||
div.className = 'step';
|
||
const hasImg = src && src.length > 50;
|
||
const img = hasImg ? '<img src="' + src + '" onclick="zoom(this.src)">'
|
||
: '<div class="noimg">无图(后端返回空)</div>';
|
||
div.innerHTML = '<div class="cap">' + title + '<small>' + (sub||'') + (hasImg ? ' ('+src.length+'字符)' : '') + '</small></div>' + img;
|
||
return div;
|
||
}
|
||
|
||
function renderResult(d) {
|
||
flog('renderResult 开始 (blend=' + (d.blend_method || '?') + ', beauty_alpha=' + d.beauty_alpha + ')', 'info');
|
||
const s = d.steps || {};
|
||
$('finalBase').src = pick(s, 'final') || '';
|
||
$('finalBase').onclick = function(){ zoom(this.src); };
|
||
$('outFull').src = pick(s, 'redraw_full') || pick(s, 'redraw_c') || '';
|
||
$('outFull').onclick = function(){ zoom(this.src); };
|
||
$('outBand').src = pick(s, 'redraw_band') || '';
|
||
$('outBand').onclick = function(){ zoom(this.src); };
|
||
const grid = $('stepsGrid'); grid.innerHTML = '';
|
||
$('stepsInfo').textContent = '(mask=pushed, blend=' + (d.blend_method || '?') + ', redraw=Flux-2, beauty_alpha=' + d.beauty_alpha + ')';
|
||
if (d.redraw && d.redraw.enabled) {
|
||
flog('重绘带 band_pixels=' + d.redraw.band_pixels + ' push_px=' + d.redraw.push_px, 'info');
|
||
if (d.redraw.c_error) flog('Flux-2 重绘失败: ' + d.redraw.c_error, 'warn');
|
||
} else if (d.redraw && d.redraw.error) {
|
||
flog('重绘带计算失败: ' + d.redraw.error, 'warn');
|
||
}
|
||
STEPS.forEach(st => {
|
||
const src = pick(s, st.key);
|
||
const hasImg = src && src.length > 50;
|
||
flog(' 渲染 ' + st.key + ': ' + (hasImg ? '有图(' + src.length + '字符)' : '无图'), hasImg ? 'info' : 'warn');
|
||
grid.appendChild(stepCard(st.title, st.sub, src));
|
||
});
|
||
flog('renderResult 完成', 'info');
|
||
}
|
||
|
||
async function submitTest() {
|
||
const file = $('imageFile').files[0];
|
||
if (!file) { setStatus('请先选择图片', 'error'); return; }
|
||
// 页面隐藏但仍提交的固定默认值
|
||
const HIDDEN = {
|
||
seg_model: 'segformer',
|
||
hairline_edge: 'column',
|
||
is_hr: 'false',
|
||
edge_erode_px: '3',
|
||
mb_feather_px: '1',
|
||
transition_band_px: '-1',
|
||
inpainting_fill: '1',
|
||
mask_blur: '11',
|
||
mask_dilate_scale: '1.0',
|
||
};
|
||
|
||
flog('===== 提交重绘(接口12) =====', 'info');
|
||
flog('前端读取 hairline_id=' + $('hairlineId').value, 'info');
|
||
flog('前端读取 hairline_push_cm=' + ($('hairlinePushCm').value || '(默认)'), 'info');
|
||
flog('前端读取 mb_levels=' + ($('mbLevels').value || '(默认)'), 'info');
|
||
flog('前端读取 blend_method=' + $('blendMethod').value, 'info');
|
||
flog('前端读取 color_match=' + $('colorMatch').checked, 'info');
|
||
flog('前端读取 color_match_strength=' + ($('cmStrength').value || '(默认)'), 'info');
|
||
flog('前端读取 beauty_alpha=' + ($('beautyAlpha').value || '(默认)'), 'info');
|
||
flog('前端读取 重绘带倍率 lo=' + ($('bandLoMult').value || '0.5') + ' hi=' + ($('bandHiMult').value || '1.5'), 'info');
|
||
flog('前端读取 comfyui_prompt=' + ($('comfyuiPrompt').value.trim() || '(默认)'), 'info');
|
||
flog('隐藏参数固定: ' + JSON.stringify(HIDDEN), 'info');
|
||
|
||
const btn = $('submitBtn');
|
||
btn.disabled = true; btn.textContent = '⏳ 重绘中...';
|
||
setStatus('正在请求(内部先跑接口11,再 Flux-2 重绘,约 15~30s)...', 'info');
|
||
$('resultsArea').classList.remove('hidden');
|
||
|
||
const form = new FormData();
|
||
form.append('image_file', file);
|
||
form.append('hairline_id', $('hairlineId').value);
|
||
form.append('gen_backend', 'swaphair');
|
||
form.append('hairgrow_strength', '0.75');
|
||
form.append('is_hr', HIDDEN.is_hr);
|
||
form.append('seg_model', HIDDEN.seg_model);
|
||
form.append('erode_cm', '0.6');
|
||
form.append('swap_mode', 'ext_mask');
|
||
form.append('edge_erode_px', HIDDEN.edge_erode_px);
|
||
form.append('denoising_strength', '0.6');
|
||
form.append('mb_levels', $('mbLevels').value || '5');
|
||
form.append('hairline_push_cm', $('hairlinePushCm').value || '0.8');
|
||
form.append('hairline_edge', HIDDEN.hairline_edge);
|
||
form.append('blend_method', $('blendMethod').value);
|
||
form.append('color_match', $('colorMatch').checked ? 'true' : 'false');
|
||
form.append('color_match_strength', $('cmStrength').value || '0.4');
|
||
form.append('beauty_alpha', $('beautyAlpha').value || '0.6');
|
||
form.append('band_lo_mult', $('bandLoMult').value || '0.5');
|
||
form.append('band_hi_mult', $('bandHiMult').value || '1.5');
|
||
form.append('mb_feather_px', HIDDEN.mb_feather_px);
|
||
form.append('transition_band_px', HIDDEN.transition_band_px);
|
||
form.append('inpainting_fill', HIDDEN.inpainting_fill);
|
||
form.append('mask_blur', HIDDEN.mask_blur);
|
||
form.append('mask_dilate_scale', HIDDEN.mask_dilate_scale);
|
||
const cp = $('comfyuiPrompt').value.trim();
|
||
if (cp) form.append('comfyui_prompt', cp);
|
||
|
||
const sentFields = {};
|
||
form.forEach((v, k) => { sentFields[k] = (k === 'image_file') ? '[文件]' : v; });
|
||
flog('实际发送的 FormData: ' + JSON.stringify(sentFields), 'info');
|
||
|
||
const t0 = performance.now();
|
||
try {
|
||
const headers = {};
|
||
const tok = $('token').value.trim();
|
||
if (tok) headers['X-Internal-Token'] = tok;
|
||
flog('fetch POST ' + ENDPOINT, 'info');
|
||
const resp = await fetch(API_BASE + ENDPOINT, { method: 'POST', headers, body: form });
|
||
flog('收到响应 http=' + resp.status, resp.ok ? 'info' : 'error');
|
||
const json = await resp.json();
|
||
const dt = ((performance.now() - t0) / 1000).toFixed(2);
|
||
flog('JSON 解析完成 code=' + json.code + ' 耗时=' + dt + 's', json.code === 0 ? 'info' : 'error');
|
||
|
||
if (json.code === 0) {
|
||
const d = json.data;
|
||
flog('后端返回 blend=' + d.blend_method + ' hairline_push_cm=' + d.hairline_push_cm + ' mask_pixels=' + d.mask_pixels, 'info');
|
||
flog('后端返回 comfyui_prompt=' + d.comfyui_prompt, 'info');
|
||
flog('后端 _rid=' + d._rid, 'info');
|
||
const s = d.steps || {};
|
||
Object.keys(s).filter(k => k.endsWith('_base64')).forEach(k => {
|
||
const len = s[k] ? s[k].length : 0;
|
||
flog(' steps.' + k + ' = ' + (len > 0 ? len + '字符' : '空'), len > 0 ? 'info' : 'warn');
|
||
});
|
||
const okRedraw = d.redraw && d.redraw.enabled && !d.redraw.c_error && (pick(s,'redraw_full'));
|
||
setStatus((okRedraw ? '✅ 重绘成功(A整帧 / B局部+美颜)' : '⚠️ 已返回(重绘可能未生效,见日志)') + ' (' + dt + 's) _rid=' + d._rid, okRedraw ? 'success' : 'error');
|
||
renderResult(d);
|
||
} else {
|
||
setStatus('❌ 业务错误 code=' + json.code + ':' + json.message, 'error');
|
||
flog('业务错误: ' + json.message, 'error');
|
||
}
|
||
} catch (err) {
|
||
setStatus('❌ 网络错误: ' + err.message, 'error');
|
||
flog('网络错误: ' + err.message, 'error');
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = '🎨 提交重绘';
|
||
}
|
||
}
|
||
|
||
function downloadFrontendLog() {
|
||
const text = FE_LOGS.map(l => '[' + l.ts + '] [' + l.level.toUpperCase() + '] ' + l.msg).join('\n');
|
||
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'iface12_frontend_log_' + Date.now() + '.txt';
|
||
a.click();
|
||
}
|
||
|
||
async function downloadBackendLog() {
|
||
flog('下载后端日志...', 'info');
|
||
try {
|
||
const resp = await fetch(API_BASE + LOG_API + '?tail=1000');
|
||
const text = await resp.text();
|
||
flog('后端日志获取成功 ' + text.length + ' 字符', 'info');
|
||
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'iface12_backend_log_' + Date.now() + '.txt';
|
||
a.click();
|
||
} catch (err) {
|
||
flog('下载后端日志失败: ' + err.message, 'error');
|
||
}
|
||
}
|
||
|
||
$('imageFile').addEventListener('change', function(){ if(this.files.length) flog('选择图片: ' + this.files[0].name, 'info'); });
|
||
|
||
flog('接口12 重绘测试页加载完成(两版对比:A整帧 / B局部加发+全脸美颜)', 'info');
|
||
flog('默认: hairline=chang_bolang, push=0.8, blend=two_stage, cm_strength=0.4, beauty_alpha=0.6;需 ComfyUI(:8188) 在跑', 'info');
|
||
</script>
|
||
</body>
|
||
</html>
|