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)