410 lines
23 KiB
HTML
410 lines
23 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>接口11 调试页(带日志)— 发际线生发</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: #2563eb; color: #fff; }
|
||
.btn-primary:disabled { background: #93c5fd; 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; }
|
||
.pf .row input[type=range] { flex: 1; }
|
||
.status { padding: 10px 16px; border-radius: 8px; font-size: 14px; margin-bottom: 16px; display: none; font-family: monospace; }
|
||
.status.info { background: #dbeafe; color: #1e40af; 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(260px, 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>接口11 调试页 <span style="font-size:13px;color:#888">(带前后端日志)</span></h1>
|
||
<div class="subtitle">
|
||
独立调试页,每一步都记录日志。提交后展开"前端日志"和"后端日志",点按钮可下载。<br>
|
||
重点排查:遮罩是否走 pushed、①-f/①-g 是否有图、最终遮罩是否正确。遮罩固定 pushed,融合默认 multiband(可调,含两段式)。
|
||
</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_zhixian">chang_zhixian(直线)</option>
|
||
<option value="chang_tuoyuan">chang_tuoyuan(椭圆)</option>
|
||
<option value="chang_bolang">chang_bolang(波浪)</option>
|
||
<option value="chang_xinxing">chang_xinxing(心形)</option>
|
||
<option value="chang_huaban">chang_huaban(花瓣)</option>
|
||
</select>
|
||
</div>
|
||
<div class="pf">
|
||
<label>seg_model <span class="desc">分割模型</span></label>
|
||
<select id="segModel">
|
||
<option value="segformer">segformer</option>
|
||
<option value="bisenet">bisenet</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="1.0"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>hairline_edge <span class="desc">发际线提取</span></label>
|
||
<select id="hairlineEdge">
|
||
<option value="column">column(逐列最低点)</option>
|
||
<option value="contour">contour(形态学轮廓)</option>
|
||
</select>
|
||
</div>
|
||
<div class="pf">
|
||
<label>is_hr <span class="desc">高清模式(换发型输出1152×1536)</span></label>
|
||
<div class="row"><input type="checkbox" id="isHr" style="width:18px;height:18px"><span class="desc">仅 swaphair 后端;关=576×768</span></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>edge_erode_px <span class="desc">贴图前遮罩内缩(px)</span></label>
|
||
<div class="row"><input type="number" id="edgeErodePx" min="0" max="40" step="1" value="3"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>blend_method <span class="desc">接缝融合方法</span></label>
|
||
<select id="blendMethod">
|
||
<option value="multiband">multiband(多频段金字塔,默认)</option>
|
||
<option value="two_stage">two_stage(泊松→多频段,大色差)</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" id="cmNote">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="1.0"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>mb_feather_px <span class="desc">最细层掩码羽化(px)</span></label>
|
||
<div class="row"><input type="number" id="mbFeatherPx" min="0" max="5" step="1" value="1"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>transition_band_px <span class="desc">过渡带边距(-1=自动)</span></label>
|
||
<div class="row"><input type="number" id="transitionBandPx" min="-1" max="128" step="1" value="-1"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>redraw <span class="desc">发际线带重绘开关</span></label>
|
||
<div class="row"><input type="checkbox" id="redraw" style="width:18px;height:18px"><span class="desc">开:发际线带(外推↔内推)重绘,final输入,swapHair/Flux-2两路对比</span></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>inpainting_fill <span class="desc">重绘填充(治染绿)</span></label>
|
||
<select id="inpaintingFill">
|
||
<option value="1">1=填充噪声(默认/原始)</option>
|
||
<option value="0">0=保留原图(治染绿)</option>
|
||
<option value="2">2=纯色填充</option>
|
||
<option value="3">3=潜变量噪声</option>
|
||
</select>
|
||
</div>
|
||
<div class="pf">
|
||
<label>mask_blur <span class="desc">重绘遮罩边缘模糊(px)</span></label>
|
||
<div class="row"><input type="number" id="maskBlur" min="0" max="64" step="1" value="11"></div>
|
||
</div>
|
||
<div class="pf">
|
||
<label>mask_dilate_scale <span class="desc">重绘遮罩膨胀缩放</span></label>
|
||
<div class="row"><input type="number" id="maskDilateScale" min="0" max="4" step="0.1" value="1.0"></div>
|
||
</div>
|
||
</div>
|
||
<div class="pf" style="margin-top:14px">
|
||
<label>comfyui_prompt <span class="desc">redraw C路(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。融合默认 multiband,可在上方 blend_method 切换对比。勾选 redraw 开启发际线带重绘(swapHair/Flux-2 两路对比)。</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(320px, 1fr))">
|
||
<div class="step big"><div class="cap">输入原图</div><img id="finalInput"></div>
|
||
<div class="step big"><div class="cap">最终结果</div><img id="finalOut"></div>
|
||
</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';
|
||
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: 'baseline_overlay', title: '①-a 发际线分割线', sub: '黄线=baseline' },
|
||
{ key: 'upper_overlay', title: '①-b 上半区', sub: '青=baseline以上' },
|
||
{ key: 'hair_seg_overlay', title: '①-c 头发分割', sub: '绿=头发像素' },
|
||
{ key: 'top_fill_overlay', title: '①-d 填充到基线', sub: '蓝=top_fill(仅eroded/closed)' },
|
||
{ key: 'closed_overlay', title: '①-e 闭合区域', sub: '紫=closed(仅eroded/closed)' },
|
||
{ key: 'hairline_overlay', title: '①-f 头发内轮廓线', sub: '绿=头发内轮廓(额头弧+两侧到下颌),黄=baseline折线(仅pushed)' },
|
||
{ key: 'pushed_overlay', title: '①-g 外推发际线', sub: '青=外推线(进头发push_cm),红=遮罩,绿=内轮廓(仅pushed)' },
|
||
{ key: 'mask_overlay', title: '① 最终遮罩(叠加)', sub: '红=最终遮罩区' },
|
||
{ key: 'mask', title: '① 纯遮罩', sub: '白=贴回区' },
|
||
{ key: 'swap_raw', title: '② 生成全帧', sub: '换发型结果' },
|
||
{ key: 'hard_paste', title: '③ 严格贴回', sub: '遮罩内=生成,外=原图' },
|
||
{ key: 'alpha', title: '④ 融合权重', sub: '白=用生成图' },
|
||
{ key: 'final', title: '④ 接缝融合(最终)', sub: '最终输出' },
|
||
{ key: 'redraw_band_overlay', title: '⑤-① 发际线重绘带', sub: '紫=外推↔内推之间的带(以内轮廓为中心,两侧各push_cm,仅redraw)' },
|
||
{ key: 'redraw_a', title: '⑤-② 带重绘 swapHair路', sub: 'final+带重绘(swapHair)→final融合(仅redraw)' },
|
||
{ key: 'redraw_c', title: '⑤-③ 带重绘 Flux-2路', sub: 'final+带重绘(Flux-2,保色不染绿)→final融合(仅redraw)' },
|
||
];
|
||
|
||
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 开始 (mask=pushed, blend=' + (d.blend_method || '?') + ')', 'info');
|
||
const s = d.steps || {};
|
||
$('finalInput').src = pick(s, 'input') || '';
|
||
$('finalInput').onclick = function(){ zoom(this.src); };
|
||
$('finalOut').src = pick(s, 'final') || '';
|
||
$('finalOut').onclick = function(){ zoom(this.src); };
|
||
const grid = $('stepsGrid'); grid.innerHTML = '';
|
||
// 固定 pushed:只展示 pushed 相关步骤(不展示 top_fill/closed)
|
||
// redraw 步骤(redraw_band/redraw_a/redraw_c)只在开启 redraw 且有图时才渲染
|
||
const showKeys = new Set(['baseline_overlay','upper_overlay','hair_seg_overlay',
|
||
'hairline_overlay','pushed_overlay','mask_overlay','mask','swap_raw','hard_paste','alpha','final',
|
||
'redraw_band_overlay','redraw_a','redraw_c']);
|
||
const redrawKeys = new Set(['redraw_band_overlay','redraw_a','redraw_c']);
|
||
$('stepsInfo').textContent = '(mask=pushed, blend=' + (d.blend_method || '?') + ')';
|
||
if (d.redraw && d.redraw.enabled) {
|
||
flog('发际线带重绘已开启 band_pixels=' + d.redraw.band_pixels, 'info');
|
||
} else if (d.redraw && d.redraw.error) {
|
||
flog('发际线带重绘失败: ' + d.redraw.error, 'warn');
|
||
}
|
||
STEPS.filter(st => showKeys.has(st.key)).forEach(st => {
|
||
const src = pick(s, st.key);
|
||
const hasImg = src && src.length > 50;
|
||
// redraw 步骤无图(未开启或失败)时跳过,不占位
|
||
if (redrawKeys.has(st.key) && !hasImg) return;
|
||
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; }
|
||
flog('===== 提交测试 =====', 'info');
|
||
// 记录前端实际读取的每个参数值(关键诊断)
|
||
flog('前端读取 hairline_push_cm=' + ($('hairlinePushCm').value || '(默认)'), 'info');
|
||
flog('前端读取 hairline_edge=' + ($('hairlineEdge').value || '(默认)'), 'info');
|
||
flog('前端读取 hairline_id=' + $('hairlineId').value, 'info');
|
||
flog('前端读取 seg_model=' + $('segModel').value, 'info');
|
||
flog('前端读取 is_hr=' + $('isHr').checked, '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('前端读取 mb_feather_px=' + ($('mbFeatherPx').value || '(默认)'), 'info');
|
||
flog('前端读取 transition_band_px=' + ($('transitionBandPx').value || '(默认)'), 'info');
|
||
flog('前端读取 redraw=' + $('redraw').checked, 'info');
|
||
flog('前端读取 inpainting_fill=' + $('inpaintingFill').value, 'info');
|
||
flog('前端读取 mask_blur=' + ($('maskBlur').value || '(默认)'), 'info');
|
||
flog('前端读取 mask_dilate_scale=' + ($('maskDilateScale').value || '(默认)'), 'info');
|
||
flog('前端读取 comfyui_prompt=' + ($('comfyuiPrompt').value.trim() || '(默认)'), 'info');
|
||
|
||
const btn = $('submitBtn');
|
||
btn.disabled = true; btn.textContent = '⏳ 请求中...';
|
||
setStatus('正在请求 (mask=pushed, blend=' + $('blendMethod').value + ')...', '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', $('isHr').checked ? 'true' : 'false');
|
||
form.append('seg_model', $('segModel').value);
|
||
form.append('swap_mode', 'ext_mask');
|
||
form.append('edge_erode_px', $('edgeErodePx').value || '3');
|
||
form.append('denoising_strength', '0.6');
|
||
form.append('mb_levels', $('mbLevels').value || '5');
|
||
form.append('hairline_push_cm', $('hairlinePushCm').value || '1.0');
|
||
form.append('hairline_edge', $('hairlineEdge').value);
|
||
form.append('blend_method', $('blendMethod').value);
|
||
form.append('color_match', $('colorMatch').checked ? 'true' : 'false');
|
||
form.append('color_match_strength', $('cmStrength').value || '1.0');
|
||
form.append('mb_feather_px', $('mbFeatherPx').value || '1');
|
||
form.append('transition_band_px', $('transitionBandPx').value || '-1');
|
||
form.append('redraw', $('redraw').checked ? 'true' : 'false');
|
||
form.append('inpainting_fill', $('inpaintingFill').value || '1');
|
||
form.append('mask_blur', $('maskBlur').value || '11');
|
||
form.append('mask_dilate_scale', $('maskDilateScale').value || '1.0');
|
||
const cp = $('comfyuiPrompt').value.trim();
|
||
if (cp) form.append('comfyui_prompt', cp);
|
||
|
||
// 记录发出去的 form 字段
|
||
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('后端返回 mask_type=' + d.mask_type + ' blend=' + d.blend_method + ' mask_pixels=' + d.mask_pixels, 'info');
|
||
flog('后端返回 hairline_push_cm=' + d.hairline_push_cm + ' hairline_edge=' + d.hairline_edge, 'info');
|
||
flog('后端返回 color_match=' + d.color_match + ' cm_strength=' + d.color_match_strength + ' mb_feather_px=' + d.mb_feather_px + ' transition_band_px=' + d.transition_band_px, 'info');
|
||
flog('后端返回 inpainting_fill=' + d.inpainting_fill + ' mask_blur=' + d.mask_blur + ' mask_dilate_scale=' + d.mask_dilate_scale, 'info');
|
||
flog('后端 _rid=' + d._rid, 'info');
|
||
// 详细记录 steps 每个字段长度
|
||
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');
|
||
});
|
||
setStatus('✅ 成功 (' + dt + 's) mask_type=' + d.mask_type + ' _rid=' + d._rid, 'success');
|
||
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 = '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 = '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('调试页加载完成', 'info');
|
||
flog('遮罩固定 pushed,融合默认 multiband(可调,含两段式 two_stage),选好图片直接提交', 'info');
|
||
</script>
|
||
</body>
|
||
</html>
|