diff --git a/static/img_downscale.js b/static/img_downscale.js index f01502f..35743a0 100644 --- a/static/img_downscale.js +++ b/static/img_downscale.js @@ -52,4 +52,35 @@ } window.downscaleImageFile = downscaleImageFile; + + /** + * 将接口返回的图片字段规范成 可用值。 + * 同时兼容: + * - 网关改写后的 *_url(http/https/相对路径) + * - worker 直连的 *_base64(原始 base64,或已带 data:image/...;base64, 前缀) + * @param {string|null|undefined} url + * @param {string|null|undefined} b64 + * @param {string} [mimeHint='image/jpeg'] 原始 base64 时的 MIME 提示(会按魔数再嗅探) + * @returns {string} + */ + function resolveImgSrc(url, b64, mimeHint) { + var v = url || b64 || ''; + if (!v || typeof v !== 'string') return ''; + v = v.trim(); + if (!v) return ''; + // 已是可用 URL / data URI / blob / 协议相对 URL + if (/^(https?:|blob:|data:|\/\/)/i.test(v)) return v; + // 同站相对路径(仅匹配明确的 /static/...;勿把 JPEG base64 的 /9j/ 前缀当路径) + if (/^\/static\//.test(v)) return v; + // 原始 base64:去掉可能残留的 data URI 前缀后再拼,避免双重前缀导致无法显示 + var raw = v.replace(/^data:image\/[a-zA-Z0-9+.-]+;base64,/i, ''); + if (!raw) return ''; + var mime = mimeHint || 'image/jpeg'; + if (raw.indexOf('iVBOR') === 0) mime = 'image/png'; + else if (raw.indexOf('/9j/') === 0) mime = 'image/jpeg'; + else if (raw.indexOf('R0lGOD') === 0) mime = 'image/gif'; + else if (raw.indexOf('UklGR') === 0) mime = 'image/webp'; + return 'data:' + mime + ';base64,' + raw; + } + window.resolveImgSrc = resolveImgSrc; })(); diff --git a/static/test_interface1.html b/static/test_interface1.html index ebd5605..3c70f34 100644 --- a/static/test_interface1.html +++ b/static/test_interface1.html @@ -62,7 +62,7 @@ @media (max-width: 768px) { .results { flex-direction: column; } } .hidden { display: none !important; } - +
@@ -159,12 +159,9 @@ async function submitTest() { if (json.code === 0) { setStatus('✅ 请求成功 (' + _elapsed + 's) — request_id: ' + json.request_id, 'success'); - // 兼容本地直连 worker(返回 *_base64)与网关(改写为 *_url)两种情况 + // 兼容本地直连 worker(*_base64)与网关(*_url) const _d = json.data || {}; - const annoUrl = _d.annotated_image_url - || (_d.annotated_image_base64 - ? ('data:image/png;base64,' + _d.annotated_image_base64.replace(/^data:image\/\w+;base64,/, '')) - : ''); + const annoUrl = resolveImgSrc(_d.annotated_image_url, _d.annotated_image_base64, 'image/png'); showOverlay(annoUrl); renderMetrics(json.data); $('metricsBar').classList.remove('hidden'); diff --git a/static/test_interface2.html b/static/test_interface2.html index 35cad4c..cc1e924 100644 --- a/static/test_interface2.html +++ b/static/test_interface2.html @@ -69,7 +69,7 @@ .hidden { display: none !important; } - +
@@ -180,9 +180,9 @@ function renderSchemes(results) { let html = ''; results.forEach((r, i) => { const lb = TYPE_LABELS[r.hairline_type] || r.hairline_type; - // 直连 worker 时后端返回 base64(网关才会改写为 *_url),URL 缺失则回退 data URI - const overlaySrc = r.image_url || (r.image_base64 ? 'data:image/png;base64,' + r.image_base64 : ''); - const grownSrc = r.grown_image_url || (r.grown_image_base64 ? 'data:image/jpeg;base64,' + r.grown_image_base64 : ''); + // 兼容网关 *_url 与 worker *_base64(原始或 data URI) + const overlaySrc = resolveImgSrc(r.image_url, r.image_base64, 'image/png'); + const grownSrc = resolveImgSrc(r.grown_image_url, r.grown_image_base64, 'image/jpeg'); // 原图 const origSlot = '
'+ @@ -206,7 +206,7 @@ function renderSchemes(results) { } else { grownSlot = '
'+ '
生发效果
'+ - '
⚠ 未返回
ComfyUI 未就绪或生成失败
'; + '
⚠ 未返回
生发失败(female=换发型/重绘,male=ComfyUI)
'; } html += '
'+ diff --git a/static/test_interface3.html b/static/test_interface3.html index a160604..806b6ec 100644 --- a/static/test_interface3.html +++ b/static/test_interface3.html @@ -71,7 +71,7 @@ @media (max-width: 800px) { .results-layout, .preview-row { flex-direction: column; } } - +
@@ -175,7 +175,7 @@ async function submitTest() { $('hairlineType').style.display = 'inline-block'; } - const grownSrc = d.hair_growth_image_url || (d.hair_growth_image_base64 ? 'data:image/jpeg;base64,' + d.hair_growth_image_base64 : ''); + const grownSrc = resolveImgSrc(d.hair_growth_image_url, d.hair_growth_image_base64, 'image/jpeg'); if (grownSrc) { $('blendTop').src = grownSrc; $('blendTop').style.display = 'block'; diff --git a/static/test_interface5.html b/static/test_interface5.html index acf6dff..a104330 100644 --- a/static/test_interface5.html +++ b/static/test_interface5.html @@ -88,7 +88,7 @@ .hidden { display: none !important; } @media (max-width: 800px) { .results-layout { flex-direction: column; } } - +
@@ -237,25 +237,30 @@ async function submitTest() { function renderGrid() { if (!_images.length) { $('resultsGrid').innerHTML = '无数据'; return; } let h = ''; - // 叠图档:原图打底 + 透明 PNG 叠加(无原图或无叠图 → 显示占位) - const overlayCell = (label, url) => (url && _origUrl) + // 叠图档:原图打底 + 透明 PNG 图层(无原图或无叠图 → 显示占位) + const overlayCell = (label, src) => (src && _origUrl) ? '
'+label+'
'+ '原图'+ - ''+label+'
' + ''+label+'
' : '
'+label+'
'; // 生发图:独立 img(已是 ComfyUI 完整人像照片) - const grownCell = (label, url) => url - ? '
'+label+'
'+label+'
' + const grownCell = (label, src) => src + ? '
'+label+'
'+label+'
' : '
'+label+'
'; _images.forEach(function(it) { + // 兼容网关 *_url 与 worker *_base64 + const mid = resolveImgSrc(it.image_middle_url, it.image_middle_base64, 'image/png'); + const high = resolveImgSrc(it.image_high_url, it.image_high_base64, 'image/png'); + const low = resolveImgSrc(it.image_low_url, it.image_low_base64, 'image/png'); + const grown = resolveImgSrc(it.grown_image_url, it.grown_image_base64, 'image/jpeg'); h += '
' + '
#' + (it.order||'—') + ' ' + (it.hairline_type||'') + - (it.grown_image_url ? '  含生发图' : '') + '
' + + (grown ? '  含生发图' : '') + '
' + '
' + - overlayCell('middle', it.image_middle_url) + - overlayCell('high', it.image_high_url) + - overlayCell('low', it.image_low_url) + - grownCell('生发图', it.grown_image_url) + + overlayCell('middle', mid) + + overlayCell('high', high) + + overlayCell('low', low) + + grownCell('生发图', grown) + '
' + '
'; }); diff --git a/static/test_interface6.html b/static/test_interface6.html index e679dda..00b6137 100644 --- a/static/test_interface6.html +++ b/static/test_interface6.html @@ -59,7 +59,7 @@ .diff-list { font-size: 12px; color: #6b7280; margin-top: 6px; line-height: 1.7; } .diff-list li { margin-left: 18px; } - +
@@ -161,12 +161,9 @@ async function submitTest() { if (json.code === 0) { setStatus('✅ 请求成功 (' + _elapsed + 's) — request_id: ' + json.request_id, 'success'); - // 兼容本地直连 worker(返回 *_base64)与网关(改写为 *_url)两种情况 + // 兼容本地直连 worker(*_base64)与网关(*_url) const _d = json.data || {}; - const annoUrl = _d.annotated_image_url - || (_d.annotated_image_base64 - ? ('data:image/png;base64,' + _d.annotated_image_base64.replace(/^data:image\/\w+;base64,/, '')) - : ''); + const annoUrl = resolveImgSrc(_d.annotated_image_url, _d.annotated_image_base64, 'image/png'); showOverlay(annoUrl); renderMetrics(json.data); $('metricsBar').classList.remove('hidden');