diff --git a/h3-console/server.py b/h3-console/server.py index 43036c0..a622f40 100644 --- a/h3-console/server.py +++ b/h3-console/server.py @@ -434,6 +434,23 @@ async def prioritize_video(jid: str): return job +@api.get("/v1/videos/{jid}/comfy_history") +async def get_comfy_history(jid: str): + with db() as con: + row = con.execute("SELECT gateway_id, backend FROM jobs WHERE id=?", (jid,)).fetchone() + if row is None: + raise HTTPException(404, "无此任务") + if not row["gateway_id"] or not row["backend"]: + raise HTTPException(409, "任务还在本地排队,尚未分配到可用后端") + try: + resp = await client.get(f"{row['backend']}/v1/videos/{row['gateway_id']}/comfy_history") + except httpx.HTTPError: + raise HTTPException(502, "查询 ComfyUI history 发不到后端,稍后再试") + if resp.status_code >= 400: + raise HTTPException(resp.status_code, resp.text) + return resp.json() + + @api.get("/v1/videos/{jid}") async def get_video(jid: str): with db() as con: diff --git a/h3-console/static/index.html b/h3-console/static/index.html index bbec832..4d58f5e 100644 --- a/h3-console/static/index.html +++ b/h3-console/static/index.html @@ -84,6 +84,11 @@ #detail .dl dd { margin: 0; word-break: break-all; } #detail video { max-width: 100%; margin-top: 12px; border-radius: 6px; } #detail .download { display: inline-block; margin-top: 10px; font-size: 13px; } + #detail .raw-block { margin-top: 14px; font-size: 13px; } + #detail .raw-block summary { cursor: pointer; opacity: .7; } + #detail .raw-block summary:hover { opacity: 1; } + #detail .raw-block pre { max-height: 400px; overflow: auto; background: rgba(128,128,128,.1); + padding: 10px; border-radius: 6px; font-size: 12px; white-space: pre-wrap; word-break: break-all; } @@ -1025,10 +1030,26 @@ async function refreshDetail() { if (imgTags || videoTags) refThumbs += `
${imgTags}${videoTags}
`; if (audioTags) refThumbs += `
${audioTags}
`; + // 2026-09-03:原始请求参数(req 原样 JSON)跟 ComfyUI history(实际发给模型的完整 + // workflow graph + 执行结果)都放页面最底部——排查问题时才需要看,默认折叠不占地方。 + // history 不跟着 resultHtml 一起渲染,是懒加载(点了才去问 ComfyUI),因为 queued/running + // 状态的任务每几秒轮询一次详情,没必要每次都多打一次 ComfyUI 的 /history。 + const rawReqHtml = `
+ 用户提交的原始参数(JSON) +
${JSON.stringify(req, null, 2).replace(/
+      
`; + const comfyHistoryHtml = j.comfy_prompt_id + ? `
+ ComfyUI History(实际发给模型的 workflow + 执行结果) +
点开后加载中…
+
` + : ''; + box.innerHTML = `
← 新建任务

${j.id}

${statusLabel(j.status)}${j.status === 'queued' && j.queue_position ? ` 排第 ${j.queue_position} 位` : ''} ${j.status === 'queued' ? ` ` : ''}${j.status === 'running' ? ` ` : ''}
+ ${resultHtml}
Prompt
${(req.prompt || '').replace(/
分区
${j.partition || '—'}
@@ -1045,11 +1066,22 @@ async function refreshDetail() {
峰值显存
${j.peak_vram_gb ? j.peak_vram_gb + ' GB' : '—'}
${refThumbs} - ${resultHtml} + ${rawReqHtml} + ${comfyHistoryHtml} `; } catch (e) { /* 暂时连不上,保留当前显示,下一轮再试 */ } } +async function loadComfyHistory(id) { + const pre = document.getElementById(`comfy-history-${id}`); + if (!pre) return; + try { + const r = await fetch(`${H3_BASE}/v1/videos/${id}/comfy_history`); + if (!r.ok) { pre.textContent = '加载失败: ' + (await r.text()); return; } + pre.textContent = JSON.stringify(await r.json(), null, 2); + } catch (e) { pre.textContent = '加载失败: ' + e.message; } +} + async function refreshJobs() { if (viewingTrash) { await loadTrash(); renderSidebar(); return; } try { diff --git a/raylight/scripts/server.py b/raylight/scripts/server.py index a72ba8f..5dbca78 100644 --- a/raylight/scripts/server.py +++ b/raylight/scripts/server.py @@ -700,6 +700,28 @@ def get_video(jid: str): return out +@app.get("/v1/videos/{jid}/comfy_history") +def get_comfy_history(jid: str): + """透传 ComfyUI 自己的 /history/{prompt_id}——带完整的原始 workflow graph + 执行结果, + 这份数据只在 ComfyUI 进程内存里,没有落盘,ComfyUI 重启(包括 start.sh 整体重启)后就 + 查不到了,不是本服务的 bug。""" + j = JOBS.get(jid) + if j is None: + raise HTTPException(404, "无此任务") + if not j.comfy_prompt_id: + raise HTTPException(409, "这条任务还没提交给 ComfyUI,没有 history") + try: + r = httpx.get(f"{COMFY_BASE}/history/{j.comfy_prompt_id}", timeout=5.0) + except Exception as e: + raise HTTPException(502, f"查询 ComfyUI history 失败: {type(e).__name__}: {e}") + if r.status_code != 200: + raise HTTPException(r.status_code, r.text) + data = r.json() + if j.comfy_prompt_id not in data: + raise HTTPException(404, "ComfyUI 没有这条 prompt_id 的 history了(大概率是 ComfyUI 进程后来重启过,内存记录被清空)") + return data[j.comfy_prompt_id] + + @app.get("/v1/videos/{jid}/file") def get_video_file(jid: str): j = JOBS.get(jid) diff --git a/raylight/server.py b/raylight/server.py index a72ba8f..5dbca78 100644 --- a/raylight/server.py +++ b/raylight/server.py @@ -700,6 +700,28 @@ def get_video(jid: str): return out +@app.get("/v1/videos/{jid}/comfy_history") +def get_comfy_history(jid: str): + """透传 ComfyUI 自己的 /history/{prompt_id}——带完整的原始 workflow graph + 执行结果, + 这份数据只在 ComfyUI 进程内存里,没有落盘,ComfyUI 重启(包括 start.sh 整体重启)后就 + 查不到了,不是本服务的 bug。""" + j = JOBS.get(jid) + if j is None: + raise HTTPException(404, "无此任务") + if not j.comfy_prompt_id: + raise HTTPException(409, "这条任务还没提交给 ComfyUI,没有 history") + try: + r = httpx.get(f"{COMFY_BASE}/history/{j.comfy_prompt_id}", timeout=5.0) + except Exception as e: + raise HTTPException(502, f"查询 ComfyUI history 失败: {type(e).__name__}: {e}") + if r.status_code != 200: + raise HTTPException(r.status_code, r.text) + data = r.json() + if j.comfy_prompt_id not in data: + raise HTTPException(404, "ComfyUI 没有这条 prompt_id 的 history了(大概率是 ComfyUI 进程后来重启过,内存记录被清空)") + return data[j.comfy_prompt_id] + + @app.get("/v1/videos/{jid}/file") def get_video_file(jid: str): j = JOBS.get(jid)