From 0c528cc281e6243e1c54ee859639afdc4ae1802b Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <1027066412@qq.com> Date: Mon, 5 Oct 2026 02:42:31 +0800 Subject: [PATCH] Every drop onto the canvas becomes a ComfyUI history step Audit of canvas writes for the Model Check gap: every path records a step except a character voice dropped on a TTS node (audio_voice_drag.js), where a reload right after the drop brought the old voice back. material_drag.js now records after any drop handler finishes, so no drop target can miss it; handlers that already record add no extra step. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + web/modules/material_drag.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e6621..4c85b36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - **Model Check, formerly Model Doctor, redone (模型检查,原“模型医生”,重做)**: when a workflow opens with models this computer does not have, a bar over the canvas says how many, and how many are here under another name or folder; **Put in** swaps those in and **Show** opens the doctor. The doctor page lists what needs you first and says why in plain words (same file found elsewhere, same size only, not on this computer, no fingerprint saved in the workflow), with a button for each: put in, use it, pick one yourself, or look it up on Civitai; the models that are fine fold away. Opening the doctor no longer changes the workflow by itself, and a model put in by the doctor or its picker no longer stays marked red by ComfyUI. The "auto-detect on startup" switch (it did nothing) and the doctor's own deep hash scan are gone: the check hashes same-size files itself, and the page points to the scan page when unscanned models could be the missing ones. - **The Model Check bar can be turned off (模型检查横条可以关掉)**: the bar now starts with "Model Check:" so it is clear it comes from the browser, not ComfyUI, and has **Don't show again**, which stops checking workflows when they open and says where to turn it back on. The same switch is in Settings → Workflows ("Check for missing models when a workflow opens", on by default); with it off, run Model Check from the rail when you want it. - **A model put in by Model Check stays put (模型检查换上的模型不会再变回去)**: ComfyUI was not told about the change, so reloading the page right after **Put in** (or Ctrl+Z) brought back the old, missing name and its red mark. Each put-in is now one step of ComfyUI's history, saved like any edit of your own. +- **A character voice dropped on a TTS node stays put (拖到 TTS 节点的角色声音刷新后还在)**: the same gap as above: reloading right after the drop brought back the node's previous voice, and Ctrl+Z skipped it. Every drop from the browser onto the canvas is now recorded as one history step. - **Prompt boxes found by what they are (提示词框按节点定义识别)**: dropping or writing a prompt (material library, Prompt Studio, translator) now finds a node's prompt boxes by ComfyUI's own definition (multiline text inputs) instead of guessing from names, so a filename or "value" box no longer takes a prompt and third-party prompt nodes are recognised. Whether a box is positive or negative follows its wiring to the sampler, not the node's title: a CLIP Text Encode connected to the negative input gets the negative prompt even if it is still called "CLIP Text Encode". Prompt Studio's "take prompts from the selected node" also reads the roles right again (it could not follow links in current ComfyUI). - **See where a dragged prompt goes (拖提示词前先看清楚落在哪)**: while you drag a prompt from the material library, every prompt box on the canvas is outlined (green for positive, red for negative, grey when it is not wired to a sampler) and the box under the pointer lights up. The label next to the pointer says exactly what releasing does, e.g. "Release: into #6's positive prompt box; the negative prompt box on the same sampler (#7) too". Drop on a box and that box is filled; the matching negative (or positive) box of the same sampler gets the other half of the material. On a node with several prompt boxes, point at the one you mean instead of letting it guess. A short message confirms what was filled; the library's receipt undoes it. Dropping a prompt plan on empty canvas used to create an empty prompt node; it now creates a positive and, when the plan has one, a negative node with their text. - **Current node (当前节点, formerly Node Assistant)**: the panel no longer splits into "actions" and "parameter sets" tabs; it is one page with the model actions on top and **Parameters** below. Parameters lists every saved set of values for this kind of node in one list — from the material library and from recipes' parameter sets, the same values saved twice shown once — and each entry says exactly what it would change on the selected node ("steps: 20 → 30"). **Apply these N** changes only those; seeds and model files stay (change the model above), and the receipt undoes it. **Save these values** keeps the node's current values in the material library, so the next node of that kind can use them. diff --git a/web/modules/material_drag.js b/web/modules/material_drag.js index 8e19cf2..e95966b 100644 --- a/web/modules/material_drag.js +++ b/web/modules/material_drag.js @@ -3,6 +3,7 @@ import { translate as t } from './locales.js'; import { text } from './ui_dom.js'; import { materialNodeHeading } from './material_inspector.js'; import { anomalousAlert } from './ui_dialog.js'; +import { recordCanvasStep } from './canvas_history.js'; let activeDrag = null; @@ -36,6 +37,8 @@ export function materialDropNode(event, canvas, graph) { * optional `onMove(node, data, event)` follows the accepted node under the pointer (or null). * Optional `passThrough(event)` leaves events over some part of the page (a drop zone of its * own) alone; `effectAllowed` defaults to 'copy'. `owner` may be null (no browser to fold). + * After a drop handler finishes the canvas is recorded as a ComfyUI history step, so a drop + * survives a reload and Ctrl+Z takes it back even when the handler forgot to. */ export function bindMaterialDrag(element, owner, { payload, accepts, drop, dropOnCanvas, targetHint, rejectHint, onStart, onMove, passThrough, effectAllowed = 'copy' }) { element.draggable = true; @@ -112,13 +115,13 @@ export function bindMaterialDrag(element, owner, { payload, accepts, drop, dropO const overCanvas = isOverCanvasSurface(event); cleanup(); if (validNode) { - try { await drop(node, data, graph, event); } + try { await drop(node, data, graph, event); recordCanvasStep(app); } catch (error) { await anomalousAlert(t(error.message) === error.message ? t('materialApplyFailed') : t(error.message)); } return; } if (!refused && dropOnCanvas && overCanvas) { const pos = getCanvasPosition(event, canvas); - try { await dropOnCanvas(event, data, graph, pos); } + try { await dropOnCanvas(event, data, graph, pos); recordCanvasStep(app); } catch (error) { await anomalousAlert(t(error.message) === error.message ? t('recipeOpenError') : t(error.message)); } return; }