310 lines
19 KiB
Markdown
310 lines
19 KiB
Markdown
# Material Library
|
|
|
|
The Material Library is a user-curated reuse layer. It does not replace
|
|
Workflow Recipes, Prompt Notes, Node Assistant, or Model Doctor. The initial
|
|
material kind is an image workflow snapshot captured from a generated PNG that
|
|
contains a complete ComfyUI UI workflow.
|
|
|
|
## Persistence and API ownership
|
|
|
|
`api/materials.py` owns material validation, persistence, asset serving, and
|
|
node-type lookup. 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 explicit image-inspection route returns
|
|
the validated workflow plus summary node blocks only after the user opens one
|
|
image detail. This lets the workbench display exact `widgets_values`, bounded
|
|
node `properties`, mode, and volatile widget indexes without allocating a
|
|
second full-PNG buffer in the browser. Direct PNG parsing remains a temporary
|
|
compatibility fallback for an older running backend. Exact metadata is kept in
|
|
a small 16-entry in-memory LRU, and duplicate workflow/widget fields are
|
|
discarded before caching. Local-model preview resolution is deferred until the
|
|
Models tab is opened and then cached with that image's metadata. 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; asset authorization
|
|
uses the same validated summary cache. Callers receive independent summary copies.
|
|
|
|
The library requests 48 summaries per page. `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 library's available `tags`. Older callers without
|
|
page/limit retain their complete summary response. Name/tag edits use
|
|
`update_material`, preserve the source workflow, and atomically replace the record.
|
|
Tags are trimmed, deduplicated without case sensitivity, and limited to 20 tags
|
|
of 60 characters each; older records without tags remain valid.
|
|
|
|
`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` is the default.
|
|
Exact `kind` remains compatible. `material_prompt_data.js` reads authoritative
|
|
detail fields for the studio: notes use `note.promptEn`, plans compose saved
|
|
parts, and workflow selections use top-level `prompt_groups`. The import drawer
|
|
pages summaries and fetches prompt text only on inspection; search covers names,
|
|
tags and node types. Side/full editors have separate view references and share
|
|
the active draft; closing or resetting their importer cancels pending requests.
|
|
|
|
Save, edit, and delete serialize their writes within the server process. Before
|
|
publishing a new record, save compares the source PNG SHA-256, material kind, and
|
|
selected node IDs against existing summaries. A duplicate returns HTTP 409 with
|
|
`status: duplicate` and the existing name/filename, without leaving new assets.
|
|
An explicit retry with `allow_duplicate: true` creates a separate copy. This is
|
|
exact-source duplicate detection, not visual similarity or model identity.
|
|
|
|
The route family is:
|
|
|
|
- `POST /anomalous/inspect_image_material`
|
|
- `POST /anomalous/save_image_material`
|
|
- `POST /anomalous/save_parameter_material`
|
|
- `POST /anomalous/save_prompt_note_material`
|
|
- `POST /anomalous/save_prompt_plan`
|
|
- `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`
|
|
|
|
## Frontend ownership
|
|
|
|
`ui_materials.js` owns the Workspace library, filters, pagination, editable
|
|
names/tags, full-workflow handoff, and library CRUD presentation.
|
|
`ui_gallery_detail.js` owns image inspection and image/node saving.
|
|
Both use `material_inspector.js` for shared metadata helpers and exact node
|
|
parameter rendering; the workbench does not import the library UI.
|
|
`ui_gallery.js` and
|
|
`ui_recipe_detail.js` only supply non-invasive gallery entry points. Main
|
|
Gallery retains click-to-view, drag, delete, and cover-selection behavior; the
|
|
material action appears only on hover and is hidden during cover selection.
|
|
`ui_doctor.js` presents Node Assistant presets; it shares node application with
|
|
the library through `ui_material_application.js` and `node_material_actions.js`.
|
|
|
|
`ui_recipe_detail.js` can publish the active Recipe parameters or the selected
|
|
Parameter Notebook as `recipe_parameter_selection`. The primary panel saves all
|
|
reusable widget-bearing nodes; the raw-node inspector supports direct single-node
|
|
save and explicit multi-selection. Prompt cards expose the same direct save.
|
|
The backend reloads the named Recipe/Parameter Notebook from its contained user
|
|
directory instead of trusting a browser-supplied workflow.
|
|
|
|
Material cards remain compact, summary-only discovery items. “View Details”
|
|
switches the library itself to a master-detail inspector: a contained reference
|
|
image stays on the left, while scope, model references, and reusable node blocks
|
|
are grouped on the right; exact widget values remain nested under each node.
|
|
Only the opened material fetches `material_full?include_workflow=0`: metadata and
|
|
scoped node blocks, with no complete source workflow. Only prompt cards initially
|
|
expand; other node cards build their parameter DOM on first expansion and reuse
|
|
it on later toggles. Expand/collapse-all follows the actual card state. Selectable
|
|
cards in the image workbench remain closed initially.
|
|
Returning to the list aborts
|
|
an unfinished request and releases the detail payload/DOM so browsing never
|
|
accumulates full workflows in browser memory.
|
|
|
|
Opening a complete snapshot explicitly requests `include_workflow=1`, which
|
|
returns the original workflow and an empty `node_blocks` array to avoid duplicate
|
|
widget payloads. The original seed and hash evidence remain intact. The server
|
|
reads, shapes, and serializes these responses in a worker thread. Selected-node
|
|
records filter before copying widget values; they never return a full workflow
|
|
and reject explicit workflow requests with HTTP 403. For legacy callers, omitting
|
|
the flag retains both workflow and node blocks for complete, openable snapshots.
|
|
Only `0`, `1`, or an omitted flag are accepted.
|
|
|
|
Search is debounced and each list request cancels its predecessor. Changing a
|
|
filter resets the page; returning from detail preserves the current filters.
|
|
Card activation supports Enter/Space as well as mouse clicks. A name/tag edit
|
|
refreshes summary cards and filter choices. Shared confirmation dialogs sit above
|
|
the image workbench, whose keyboard shortcuts yield while a dialog is open.
|
|
|
|
A full material workflow is exact and retains its seed. Node-sized reuse is a
|
|
preset operation and therefore uses the existing transactional parameter
|
|
application path, which skips known volatile seed widgets. The lookup endpoint
|
|
filters blocks by exact `node.type`; when more than one source node matches, the
|
|
user chooses the block explicitly.
|
|
|
|
Every node card in the image workbench can be saved directly as an
|
|
`image_node_selection`; checking several cards exposes one colocated save action
|
|
above the node list. The initially hidden footer remains dedicated to the full image and
|
|
workflow snapshot rather than mixing both concepts in a scope selector. The
|
|
original workflow remains in a selected-node record as source provenance, but
|
|
list/count/lookup APIs expose only the selected blocks. Such a material
|
|
deliberately lacks `open_workflow`; the library and Node Assistant apply its
|
|
parameters to one node without opening the hidden source workflow.
|
|
|
|
The image workbench also exposes saving on its primary surfaces. The top action
|
|
reveals the full-snapshot name/tag form. One generation-settings action captures
|
|
the sampler and size nodes together; each prompt node can be saved without visiting
|
|
the all-nodes tab. A metric action is intentionally node-sized: it does not claim
|
|
to persist one isolated widget value. The former plain-text “share text” action
|
|
is not part of this workbench.
|
|
|
|
Prompt roles are inferred from workflow topology first, with node-title hints as
|
|
a fallback. Image details display the inferred role beside every prompt node and
|
|
allow an explicit positive, negative, shared, unknown, or ignored override.
|
|
Overrides travel into the saved material but never write back to a source Recipe.
|
|
Material details expose the same selector; `update_material` atomically persists
|
|
or clears these overrides, and choosing Automatic restores the topology result.
|
|
|
|
## Model identity handoff
|
|
|
|
Material storage preserves the workflow's existing `extra.anomalous_hashes`.
|
|
When one node block is applied, records scoped to the source node ID are copied
|
|
to the target node ID. This is evidence transport, not identity resolution.
|
|
Model Doctor remains the authority that decides whether a missing model may be
|
|
repaired. A material name, preview, saved path, or size is never promoted to
|
|
cryptographic identity.
|
|
|
|
## Compatibility and future material kinds
|
|
|
|
Records declare `schema_version`, `kind`, and `capabilities`. New sources such
|
|
as Recipe selections and Prompt Note blocks should add explicit kinds or
|
|
source metadata without weakening the image snapshot contract. Existing direct
|
|
Recipe and Prompt Note use paths remain available; the library is optional
|
|
curation rather than a mandatory intermediary.
|
|
|
|
The currently saved kinds are `image_workflow_snapshot`, `image_node_selection`,
|
|
and `recipe_parameter_selection`. Recipe parameter materials intentionally have
|
|
no image asset and only advertise `apply_node_parameters`; they cannot replace
|
|
the canvas with the source Recipe. A prompt badge on a CLIPTextEncode selection
|
|
still describes a workflow prompt node, not a Prompt Note import. Prompt Notes use the explicit kinds described below. The `reference_image` capability currently
|
|
means a preserved image that can be viewed; copying it into ComfyUI input or
|
|
configuring LoadImage is also future work.
|
|
|
|
## Prompt Note capture and return
|
|
|
|
`POST save_prompt_note_material` accepts `notebook_filename` as a provenance
|
|
label, `name`, optional `tags`, `scope` (`note` or `prompt`), and an immutable
|
|
client snapshot in `note`. It does not use that filename to read a file. The
|
|
snapshot includes the latest text before the editor's autosave timer fires.
|
|
The server bounds it to the notebook's 2 MiB limit and retains only the supported
|
|
prompt, translation, language, base-model, main-model and LoRA fields. Model
|
|
objects remain saved selections, never identity evidence. Prompt-only capture
|
|
excludes all model fields. `promptZh` is a translation, not a negative prompt.
|
|
|
|
- `prompt_note_bundle`: entire note, translations and companion model selections.
|
|
- `prompt_text`: prompt text and translations without companion models.
|
|
|
|
Both kinds advertise `copy_prompt` and `restore_prompt_note`, contain no image
|
|
assets or synthetic workflow, and never appear in node-type lookup. Explicit
|
|
full-workflow requests return 403. Atomic creation and duplicate confirmation
|
|
reuse the image-free material writer; deduplication compares content signature
|
|
and kind. Editing a note later cannot alter its captured material.
|
|
|
|
The library copies prompt text or loads the snapshot as a newly named, uniquely
|
|
identified Prompt Note. It flushes any pending note before switching, writes the
|
|
new record successfully before navigation, and preserves both original note and
|
|
material. Canvas use remains the existing explicit Prompt Note action.
|
|
|
|
`material_feedback.js` supplies a shared save receipt with View Material.
|
|
`web/main.js` binds `openSavedMaterial` from `ui_materials.js`; it owns navigation
|
|
and workspace return state, keeping workbench imports acyclic. Image details,
|
|
recipes and Prompt Notes use that same handoff. Node-sized receipts lead to a
|
|
detail that explains reuse through the library or Node Assistant.
|
|
|
|
Primary surfaces use progressive disclosure: image save form, prompt role
|
|
selectors, technical node metadata, companion models, recipe export/source
|
|
editing, and recipe multi-selection are revealed on request. Parameter-page
|
|
metrics appear once with exact-value copy controls. Display wording is Parameter
|
|
Sets (参数方案); existing notebook routes and storage identifiers stay stable.
|
|
|
|
|
|
## Entry points and shared application
|
|
|
|
The lower-left control opens a standalone Material Library container, with no
|
|
workspace tabs and no initial Prompt Note fetch. The upper-right control opens
|
|
Workflow Recipes; that workspace retains its Recipes and Prompt Notes tabs.
|
|
Both containers share the browser's panel area but have separate headers and
|
|
lifecycle state; switching restores the appropriate container. Import/export is secondary under the library's More
|
|
menu, linking existing workflow tools and recipe packages, plus prompt-plan JSON
|
|
import. It does not claim to import/export arbitrary image material bundles.
|
|
|
|
Opening the library with exactly one live selected node enables apply mode.
|
|
Summary requests filter by exact `node_type`; activating a card fetches only that
|
|
material's scoped detail. No selection means normal detail browsing. An explicit
|
|
toggle returns to browsing all materials; prompt-only kinds also use browse mode.
|
|
Multiple source blocks of the same type require choosing one block. The full
|
|
workflow quick-open button is omitted from apply-mode cards.
|
|
|
|
`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. This path does not invoke global model
|
|
repair after application. Hash transport remains evidence, never verification.
|
|
|
|
`material_full?include_workflow=0` includes `workflow_hashes` restricted to returned
|
|
blocks, without exposing the hidden workflow. Both library and assistant show the
|
|
same application receipt. Undo restores values and scoped hashes only while the
|
|
same live node, values, serialized values and target hashes still match the applied
|
|
state. Later edits are protected. Unsupported third-party widget side effects
|
|
still require real-host compatibility testing.
|
|
|
|
Selection hooks chain the host's callbacks and batch updates in a microtask;
|
|
there is no polling. List browsing retains 48-item pagination and cancellation.
|
|
Language changes rebuild visible library/composer text while keeping the current
|
|
draft. Node labels reuse ComfyUI's registered localized titles with raw type fallback.
|
|
|
|
## Prompt combinations
|
|
|
|
`prompt_plan` is an image-free material with `compose_prompt` capability and no
|
|
synthetic workflow. `POST save_prompt_plan` accepts a name, tags, and `plan`:
|
|
`parts` is an ordered list of up to 100 records with `name` (up to 120 characters),
|
|
`category` (`general` or `specific`), `enabled`, `positive`, and `negative`.
|
|
Top-level `positive` and `negative` hold prompt text. The plan is bounded to
|
|
2 MiB; unknown fields are discarded. Atomic writes and explicit duplicate-copy
|
|
confirmation reuse the material persistence lock and canonical content signature.
|
|
|
|
The current panel has simple positive/negative text fields and a beginning/end
|
|
drop-position selector. Fragment categories, ordering and enable controls are
|
|
deferred. Opening an older plan joins its enabled fragments and final content
|
|
into those two fields; a new save has empty `parts`, leaving the original record
|
|
intact. Text insertion preserves weights, commas, duplicates and original nonblank
|
|
string whitespace. A Prompt Note's `promptZh` translation is never inferred as
|
|
negative text. Captured notes and classified workflow prompt groups can populate
|
|
the fields; unknown workflow roles are not silently assigned a role.
|
|
|
|
Saving creates an independent new snapshot. There is no automatic source sync or
|
|
model binding. A page-session draft survives library navigation and is replaced
|
|
by an existing saved plan only after confirmation; it must be saved before page
|
|
reload. Stale detail requests cannot replace the current draft. Export/import use
|
|
JSON format `anomalous-prompt-plan-v1`; imports are bounded to 2 MiB and validated
|
|
by the same save endpoint. The panel also exposes role and target-widget selection,
|
|
beginning/end buttons for a selected node, copy controls and guarded undo.
|
|
|
|
## Canvas drag and drop
|
|
|
|
`material_drag.js` binds native drag handles to parameter-bearing library cards
|
|
and to each prompt field's drag button. Only an active, same-page drag is trusted;
|
|
transfer data is a marker, not an external mutation command. During drag, the
|
|
browser window becomes transparent and stops intercepting pointer events. End,
|
|
drop, Escape and loss of focus restore it and remove temporary event listeners.
|
|
There is no persistent drag polling or detail request during hover.
|
|
|
|
Hit testing uses the host canvas coordinate conversion (including pan/zoom),
|
|
canvas bounds and the graph's live node lookup. Canvas and DOM-widget surfaces are
|
|
accepted. The graph/canvas identities are captured at drag start and checked again
|
|
at drop; async material application rechecks the graph/node after loading detail.
|
|
The actual drop node is the target, regardless of the previously selected node.
|
|
Receipts name that target and provide the shared guarded undo.
|
|
|
|
Parameter-bearing materials fetch their scoped node blocks only after drop and
|
|
replace compatible widget values through the same transaction as Node Assistant,
|
|
preserving seed, node position and links. Multiple matching blocks require a
|
|
choice. No full workflow is loaded. Prompt-panel drops instead insert the chosen
|
|
role's text before or after existing node text; multiple eligible text widgets
|
|
require choosing one. Empty/unsupported targets and canceled drags do not mutate
|
|
the graph. Neither drop path queues generation. Actual host drag visuals and
|
|
third-party/Vue node compatibility remain part of the deferred browser acceptance.
|