From 27392c5d7e987fd32f0b203fdea4541380c8c200 Mon Sep 17 00:00:00 2001 From: yibo Date: Mon, 24 Aug 2026 13:08:52 +0800 Subject: [PATCH] 新建任务表单:参考图/视频/音频/首尾帧支持单个删除 --- h3-console/static/index.html | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/h3-console/static/index.html b/h3-console/static/index.html index af95841..af84735 100644 --- a/h3-console/static/index.html +++ b/h3-console/static/index.html @@ -52,6 +52,11 @@ .thumbrow img { height: 60px; border-radius: 4px; } .thumbrow video { height: 220px; max-width: 100%; border-radius: 4px; } .thumbrow audio { height: 32px; width: 220px; } + .thumb-wrap { position: relative; display: inline-block; } + .thumb-del { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; padding: 0; + border-radius: 50%; border: none; background: #c33; color: #fff; font-size: 13px; line-height: 1; + cursor: pointer; } + .thumb-del:hover { background: #a22; } .status-done { color: #2a7; } .status-failed { color: #c33; } .status-running { color: #a70; } .status-queued { color: #777; } .status-cancelled { color: #999; } .err { color: #c33; font-size: 13px; margin-top: 8px; white-space: pre-wrap; font-family: ui-monospace, Menlo, Consolas, monospace; max-height: 400px; overflow-y: auto; background: #fff5f5; border: 1px solid #f3c2c2; border-radius: 6px; padding: 10px; } @@ -322,40 +327,76 @@ async function uploadFile(file) { return await r.json(); } +// 每个预览项都包一层 .thumb-wrap 挂一个 × 删除按钮——上传上去选错了/传多了, +// 之前只能整个表单清空重来,2026-08-24 用户要求能单个删。 +function wrapWithDelete(mediaEl, onDelete) { + const wrap = document.createElement('span'); + wrap.className = 'thumb-wrap'; + const del = document.createElement('button'); + del.type = 'button'; del.className = 'thumb-del'; del.textContent = '×'; del.title = '删除'; + del.onclick = onDelete; + wrap.appendChild(mediaEl); wrap.appendChild(del); + return wrap; +} + +function renderFl2vaThumbs() { + const box = document.getElementById('fl2va-thumbs'); box.innerHTML = ''; + [['fl2va-first', '首帧'], ['fl2va-last', '尾帧']].forEach(([key, label]) => { + const u = uploaded[key]; + if (!u) return; + const img = document.createElement('img'); img.src = u; img.title = label; + box.appendChild(wrapWithDelete(img, () => { + uploaded[key] = null; + document.getElementById(key).value = ''; // 清掉file input,不然重选同一个文件不会触发change + renderFl2vaThumbs(); + })); + }); +} + async function uploadTo(inputId) { const el = document.getElementById(inputId); if (!el.files[0]) return; try { const res = await uploadFile(el.files[0]); uploaded[inputId] = res.url; - const thumbs = document.getElementById('fl2va-thumbs'); - const img = document.createElement('img'); img.src = res.url; thumbs.appendChild(img); + renderFl2vaThumbs(); } catch (e) { document.getElementById('fl2va-err').textContent = '上传失败: ' + e.message; } } +function renderRefThumbs() { + const box = document.getElementById('ref2va-thumbs'); box.innerHTML = ''; + uploaded['ref2va-refs'].forEach((u, i) => { + const img = document.createElement('img'); img.src = u; + box.appendChild(wrapWithDelete(img, () => { + uploaded['ref2va-refs'].splice(i, 1); + renderRefThumbs(); + })); + }); +} + async function uploadMulti() { const el = document.getElementById('ref2va-refs'); - const thumbs = document.getElementById('ref2va-thumbs'); for (const f of el.files) { try { const res = await uploadFile(f); uploaded['ref2va-refs'].push(res.url); - const img = document.createElement('img'); img.src = res.url; thumbs.appendChild(img); } catch (e) { document.getElementById('ref2va-err').textContent = '上传失败: ' + e.message; } } + renderRefThumbs(); } // 参考视频/独立音频参考共用——不像参考图没有上限,这两个都封顶3个(跟节点协议一致), -// 视频/音频不方便像图片一样直接预览,用文件名文字标签代替。 -// elId 里带 "video"/"audio" 判断放哪种播放器——跟生成结果的视频一样直接能播放预览, -// 不再只显示文件名。 -function renderTags(elId, urls) { +// elId 里带 "video"/"audio" 判断放哪种播放器——跟生成结果的视频一样直接能播放预览。 +function renderTags(elId, stateKey) { const box = document.getElementById(elId); box.innerHTML = ''; const isVideo = elId.includes('video'); - urls.forEach(u => { + uploaded[stateKey].forEach((u, i) => { const el = document.createElement(isVideo ? 'video' : 'audio'); el.src = u; el.controls = true; - box.appendChild(el); + box.appendChild(wrapWithDelete(el, () => { + uploaded[stateKey].splice(i, 1); + renderTags(elId, stateKey); + })); }); } @@ -369,7 +410,7 @@ async function uploadMultiTagged(inputId, stateKey, tagsElId) { uploaded[stateKey].push(res.url); } catch (e) { errBox.textContent = '上传失败: ' + e.message; } } - renderTags(tagsElId, uploaded[stateKey]); + renderTags(tagsElId, stateKey); } // 分辨率/时长/Turbo/步数/Seed 五个字段,三个 tab 结构完全一样,抽出来一份读取逻辑。 @@ -430,11 +471,6 @@ async function submitJob(kind) { // 重跑:不直接提交,而是把这条任务当初的 req 灌回"新建任务"表单(对应的 tab、参数、 // 已上传的图片 URL 都原样带过去),跳到 /newtask.html,用户看一眼确认无误后自己点提交—— // 跟正常新建任务走的是同一条路径、同一次人工确认,不是绕过表单直接后台再发一次请求。 -function setThumbs(elId, urls) { - const box = document.getElementById(elId); box.innerHTML = ''; - urls.forEach(u => { if (!u) return; const img = document.createElement('img'); img.src = u; box.appendChild(img); }); -} - function prefillCreate(req) { const kind = (req.first_frame_url || req.last_frame_url) ? 'fl2va' : ((req.reference_image_urls || []).length || (req.reference_video_urls || []).length || (req.reference_audio_urls || []).length) ? 'ref2va' : 'text'; @@ -474,14 +510,14 @@ function prefillCreate(req) { if (kind === 'fl2va') { uploaded['fl2va-first'] = req.first_frame_url || null; uploaded['fl2va-last'] = req.last_frame_url || null; - setThumbs('fl2va-thumbs', [req.first_frame_url, req.last_frame_url]); + renderFl2vaThumbs(); } else if (kind === 'ref2va') { uploaded['ref2va-refs'] = (req.reference_image_urls || []).slice(); uploaded['ref2va-videos'] = (req.reference_video_urls || []).slice(); uploaded['ref2va-audios'] = (req.reference_audio_urls || []).slice(); - setThumbs('ref2va-thumbs', uploaded['ref2va-refs']); - renderTags('ref2va-video-tags', uploaded['ref2va-videos']); - renderTags('ref2va-audio-tags', uploaded['ref2va-audios']); + renderRefThumbs(); + renderTags('ref2va-video-tags', 'ref2va-videos'); + renderTags('ref2va-audio-tags', 'ref2va-audios'); } showCreate(); -- libgit2 0.21.4