Files
DemonGatanjieuandClaude Opus 5.5 186f5467b0 Lay a workflow's details out as flow, preset tabs and the preset shown
The left column is gone. The details are one column: the flow, a row of
tabs under it (the workflow's own values, each preset with how many
values it changes, + to save the canvas or copy the one shown, all
pictures and versions at the end; it stays in view while the panel
scrolls) and the tab chosen in a panel joined to it.

The flow is small and shows only the structure: the stages in the order
they run, then the finishing nodes and the saves; above them one lane
each for the model files, the LoRAs and the control models, each a bar
over the stages it feeds (recipe_flow.flowLanes, tested). Steps, splits
and strengths moved into the boxes' tooltips; the legend and the hint
line are gone. What the preset shown changes from the workflow's own is
orange, and its head says how many values that is. A box's values open
under the flow only once one is picked, with x to close.

The set rows' key-value chips went with the column; the tabs count the
differences with ui_param_set_keys.setDifferences. Remaining "方案"
labels in the preset menus now say 预设.

Checked on the isolated test ComfyUI with the two-pass Anima card: each
preset marks the right boxes, the inspector, the + menu and its editor,
all pictures, versions, sticky tabs, and the narrow (docked) layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:05:38 +08:00

13 KiB

Saved prompts and node values (material files)

Material files hold what the user keeps for reuse that is not a whole workflow or a combo: saved prompts and saved node values. There is no Material Library page any more; each kind shows where it is used:

  • Prompt kinds (prompt_plan, older prompt_text / prompt_note_bundle, and selections made only of prompt nodes) are Prompt Studio's library cards (prompt_material_source.js), renamed, deleted and dragged onto the canvas there.
  • Value kinds (node_parameter_selection, image_node_selection, recipe_parameter_selection) are listed per node type in Current node (node_parameter_sets.js, with the recipes' own parameter sets), applied and deleted there.
  • image_workflow_snapshot records are whole workflows kept before recipes took over; the Workflows page offers to move them to recipes. Their node blocks still count as saved values for Current node.

Whole workflows are Workflow Recipes; a main model, LoRAs and a prompt kept together are combos (anomalous_notebooks). Neither is copied into material files.

Persistence and API ownership

api/material_schema.py owns record shaping, prompt-role rules and bounded normalization; api/material_assets.py owns source inspection and private image copies; api/material_store.py is the sole owner of persistence locking, the summary cache, query/update/delete, and recipe-source resolution. api/materials.py owns HTTP request/response mapping and narrow compatibility entry points; api/node_material.py saves one canvas node's current values. Records live under user/<profile>/workflows/anomalous_materials; source PNG copies and bounded WebP previews live in a private .assets/<material-stem>/ directory.

The backend accepts output-image descriptors only after applying the shared filename and containment checks. It reads bounded embedded metadata, requires a valid UI workflow, copies at most 64 MiB of source image data, validates the graph with the Recipe workflow validator, and writes JSON atomically. List responses omit the full workflow. The image-inspection route returns the validated workflow plus summary node blocks for the image workbench, which keeps exact metadata in a small 16-entry in-memory LRU. Asset reads require both a valid material record and a contained private asset path.

Saving stages the image, preview, and JSON together below a private temporary directory. Assets are promoted before the record becomes visible. A failed final record commit removes only the newly promoted assets; staging is cleaned on exit. Existing records and their images are never overwritten by this path.

Material discovery caches up to 4,096 summaries, keyed by the contained record's real path, size, mtime, and ctime. It never caches full workflows. A directory inventory detects additions/deletions and re-parses only changed records. Node-type lookup filters summaries before opening matching workflows. Callers receive independent summary copies.

materials accepts q (name, tags, or node type), tag, kind, category, exact node_type, page, and limit (at most 100), and returns total, page, pages, and the available tags; callers without page/limit get every summary. category groups sources before pagination: workflow contains full snapshots; prompts contains note/text/plan kinds and selections consisting only of prompt nodes; params contains other node/recipe selections; all (the default) is every category but workflow. A snapshot moved to a recipe (mark_material_moved records moved_to_recipe; the file stays) is listed in no category. Name/tag edits use update_material and atomically replace the record. Tags are trimmed, deduplicated without case sensitivity, and limited to 20 tags of 60 characters each.

Save, edit, and delete serialize their writes within the server process. An image save compares the source PNG SHA-256, material kind, and selected node IDs, and a prompt plan or node values compare their content signature, against existing summaries. A duplicate returns HTTP 409 with status: duplicate and the existing name/filename, without leaving new assets; allow_duplicate: true creates a copy. Deleting moves the record and its assets to the Recycle Bin.

The route family is:

  • POST /anomalous/inspect_image_material
  • POST /anomalous/save_image_material
  • POST /anomalous/save_prompt_plan
  • POST /anomalous/save_node_material
  • POST /anomalous/mark_material_moved
  • GET /anomalous/materials
  • GET /anomalous/material_full
  • GET /anomalous/material_asset
  • GET /anomalous/materials/by_node_type
  • POST /anomalous/update_material
  • POST /anomalous/delete_material

materials/by_node_type (Current node's saved values) also says where each record's values came from: origin (the source type, generated_image or canvas_node), source_image (the kept picture's filename and subfolder, or null) and preview (the saved preview asset's id, for material_asset, or "").

material_full?include_workflow=0 returns metadata and scoped node blocks (with workflow_hashes restricted to them) and no complete workflow; include_workflow=1 returns the original workflow of a complete snapshot. Selected-node records never return a full workflow and reject explicit workflow requests with HTTP 403.

Kinds

Records declare schema_version, kind, and capabilities.

  • image_workflow_snapshot: a generated PNG's complete UI workflow (no longer created; kept images are recipes).
  • image_node_selection: some nodes of an image's workflow, saved from the image workbench (a node card, checked cards, or the generation settings). The workflow stays as provenance; list, count and lookup expose only the selected blocks.
  • recipe_parameter_selection: nodes of a recipe or one of its parameter sets, saved before Current node listed recipe sets itself (no longer created). No image asset; only apply_node_parameters.
  • node_parameter_selection: one canvas node's values, saved from Current node.
  • prompt_plan: an image-free prompt with compose_prompt (below).
  • prompt_text / prompt_note_bundle: prompts copied from Prompt Notes before combos; read as prompt cards (note.promptEn).

Prompt roles in image selections are inferred from workflow topology first, with node-title hints as a fallback. The image workbench shows the inferred role beside every prompt node and allows a positive, negative, shared, unknown, or ignored override, which travels into the saved record but never writes back to a Recipe.

Keeping an output image

The gallery's star and the image workbench's Keep (ui_keep_menu.js) keep an output PNG as one of three things, each recording the image as its source: the whole workflow as a Workflow Recipe (recipe_save.js, source_image), the nodes that make its sampler's model and prompts as a node-structure combo (image_keep_nodes.js, sampler_inputs.js: loaders, LoRA chains, CLIP skip, model patches and both prompt nodes, every value as it was; a combo never changes a value) — or, for a workflow with no sampler, its main model, LoRAs and positive prompt as a combo (image_keep.js; models are looked up among this computer's files with /anomalous/resolve_paths_to_previews, missing ones are left out; the notebook file keeps source_image, which the Combos card and the editor's title show; the name is the first LoRA's (else the main model's) · the prompt's first two tags that are neither quality words nor ones nearly every picture has, and the toast lets the user rename it at once), or its positive and negative prompts as a prompt_plan material (/anomalous/save_prompt_plan stores source.image; the same plan saved again answers 409 with the existing one). The image's workflow is laid on a detached graph and read with extractRecipeMetadata, so prompts follow the wiring as in a recipe. GET /anomalous/kept_images (api/kept_images.py) lists each image's recipe, combo and prompt; the star is filled when any exists, and a kept row opens it or removes it: what was kept goes to the Recycle Bin through its own delete route (delete_recipe, delete_notebook, delete_material) after a confirmation, and the star follows.

Comparing parameters

param_compare.js reads two sides (readParams: each node's named widget values, mode, input wiring and prompt-box role from prompt_boxes.js) and compares them (compareParams): nodes pair by id and type, then leftover nodes by type when each side has exactly one of it; values equal by their text form are counted, not listed; seeds (isVolatileWidget) are flagged. textDifference shows a long text as the tags only one side has (each once), or as the differing span with 40 characters around it; the window gives each side its own line and colour, since neither side is older than the other. ui_param_compare.js is the window: a side is an output image (its workflow from inspect_image_material, laid on a detached graph) or the live canvas. With the canvas on one side, ticked image values are written through applyNodeMaterialValues (seeds, missing choices and vanished nodes skipped and counted), all as one Ctrl+Z step, with Undo in the toast. Nothing is stored; the gallery's pick (owner.comparePick) lasts until it is used or cancelled.

An image's workflow is always laid on its graph through detached_graph.js, which gives the copy a fresh id: ComfyUI's frontend keeps widget values by root graph id, node and name, and a copy under the open canvas's id (any workflow opened from that image) would share and overwrite the canvas's values.

Key parameters (key_params.js) are starred in the window by node type and widget name, so a star holds in every workflow; they live in the hidden ComfyUI setting Anomalous.ModelBrowser.KeyParameters (at most 40) and show first, equal values included. A side can also be a saved workflow ({ workflow, label }): a recipe's parameter set beside the recipe (ui_param_set_keys.js), whose tab in the details says how many values differ. GET /anomalous/parameters/signatures gives each recipe's sets by their value signature (files over the save limit skipped, at most 5,000 read); a recipe card counts those unlike the recipe's own, since saving a recipe also keeps a set equal to it.

Prompt plans

prompt_plan has no synthetic workflow. POST save_prompt_plan accepts a name, tags, an optional source_image, and plan: parts is an ordered list of up to 100 records with name (up to 120 characters), category, role, enabled, positive, and negative; top-level positive and negative hold the composed text. The plan is bounded to 2 MiB; unknown fields are discarded. material_prompt_data.js reads the text Prompt Studio shows: plans compose their parts, notes use note.promptEn, and workflow selections use prompt_groups.

Applying saved values

node_material_actions.js is the shared, UI-independent mutation owner. It checks live graph/node identity, indexes, value types and native combo choices before editing. It skips seed widgets, preserves node identity/links/position, calls the live widget callback and four-argument node hook, and marks the graph dirty. Values, serialized widget values and target-scoped hash evidence change in one before/after transaction; hook failures restore their snapshots. Model paths must already be available in the native combo. Current node writes only the values that differ (never seeds, model files or choices this computer lacks) and shows the receipt with Undo (ui_apply_receipt.js); Undo restores values only while the same live node still holds the applied state. Material storage preserves a workflow's extra.anomalous_hashes, but applying saved values leaves model files and their hash evidence as they are; Model Check remains the authority on whether a missing model may be repaired, and a saved name, preview, path or size is never treated as identity.

Dragging prompts onto the canvas

Prompt Studio's cards drag onto the canvas (prompt_card_drag.js through material_drag.js). Only an active, same-page drag is trusted; transfer data is a marker, not an external mutation command. Over the studio drawer the drag passes through to the studio's prompt boxes. Live node lookup uses ComfyUI canvas coordinate conversion, canvas bounds and graph hit-testing; DOM-widget surfaces are accepted, and the graph/canvas identities captured at drag start are checked again at drop.

While a prompt is dragged (prompt_drop.js), every prompt box on the canvas is outlined in its role's colour (prompt_boxes.js: a multiline STRING input by the node's definition; its role is its own name or the wiring to a sampler). The box under the pointer is the target (a node with one box needs no aim), the hint names the node and box, and the text a release would write is laid over the box (.anomalous-prompt-preview, never in the widget's value). A card fills the box with its text as one undo step; negative text never goes into a positive box. Released on empty canvas, a card becomes a CLIPTextEncode node of its role with its text. Empty or refusing targets and cancelled drags do not change the graph; nothing queues generation.