Pair category repair (#550) and saved instructions + dialog picker (#431) (#590)

* feat(maintenance): repair old Pair prompts' empty category and tags (#550)

Squashed from PR #579. Closes #550.

* feat(tagging): saved instructions with a default per engine (#431)

Squashed from PR #587.

* feat(tagging): pick saved instructions in the tagging dialogs (#431)

Squashed from PR #588. Closes #431.
This commit is contained in:
Vito
2026-11-01 03:01:07 +00:00
committed by GitHub
parent 9640ca86f8
commit 3295ff8b20
44 changed files with 3905 additions and 207 deletions
+140 -12
View File
@@ -1886,7 +1886,8 @@ Inspect plan keeps D60–D66 (plan D60 → D83, D61 → D84, D65 → D88, D68
engines in the mock's order with legacy labels and sizes (`WD14 SwinV2 · ~400
MB`, `WD14 ViT · ~400 MB`, `GGUF JoyCaption (Recommended) · ~4.5 GB` in the
mock's Ollama slot, `HuggingFace JoyCaption · ~16 GB`); the instructions start
from the server's `default_prompt`.
from the chosen engine's default saved instructions (#431, D204; before, the
server's `default_prompt`).
- **D91** (plan D68, ruling T3-bulk): a **Suggestions** number field (1–100) for
every model, prefilled from `autotag.defaults.suggestion_limit` (10); a model
change keeps it.
@@ -2978,7 +2979,8 @@ change: ComfyUI needs a restart.
A Pair save, by a run or the node's Save pair button, writes the category and tags to the positive prompt only when that
save creates the prompt row (maintainer ruling). An existing prompt never changes on a run: empty, cleared or set by a person, including one first seen as
another pair's negative. The negative stays empty, the saved pair keeps its own metadata, and `save_prompt_pair` writes
no prompt metadata unless called with `fill_new_positive`. No backfill; optional repair is #550.
no prompt metadata unless called with `fill_new_positive`. No backfill on a run; the opt-in repair for prompts older
Pair runs saved is System › Maintenance › "Pair category and tags" (#550, "Pair category and tags repair" below).
Tests: `tests/test_prompt_pair_node.py`, `tests/test_api_pairs.py`, `tests/comfyui/pair-node.spec.mjs`. A Python change: ComfyUI needs a restart.
### Delete… on the archived shelf (#492)
@@ -4307,6 +4309,12 @@ the per-section hand-off panel, was retired by S12 (#329): every section is nati
primary stays the concept's plain button; the plan it opens has the primary.
`duplicates dialog matches the concept` and `duplicates comparison detects mutations`
(system-maintenance-concept.spec) pin the mapping.
- **D192** (Maintenance, #550, beyond the concept): Database adds "Pair category and
tags" after "Unused prompt components", a plain Review plan row (no danger mark: it
deletes nothing) with the line "Fill an empty category or tags on prompts older Pair
node runs saved, from their saved pairs. Only empty fields of never-edited prompts."
`system-maintenance-concept.spec.mjs` maps it (`mapPairRow`) as a copy of Optimize
database's row.
- **D102** (Maintenance, S7, Q5, #222): Metadata cache clears only the browser entries
Prompt Manager owns as a cache (`web/js/cache-ownership.js`). The inventory of every
storage writer has none (see "Browser cache ownership" below), so the row's line adds
@@ -4606,9 +4614,9 @@ the per-section hand-off panel, was retired by S12 (#329): every section is nati
engine and policy (read-only) and opens "Review tag output images" in the concept's
record-changing review (its `maintenancePlan` shell): `h3` "Tag output images", the
`dl.meta-grid` Model, Existing tags, Images, Already auto-tagged, Images to tag
("about N"), Time, Settings ("General <g> · Character <c>" or "Template of N
characters", then " · up to L tags per image" with the preview's saved
`suggestion_limit`) and Backup, the estimate sentence ("Nothing is written until you
("about N"), Time, Settings ("General <g> · Character <c>", then " · up to L tags
per image" with the preview's saved `suggestion_limit`; for a language model, since
D205, an "Instructions" row and "Up to L tags per image") and Backup, the estimate sentence ("Nothing is written until you
start. The number to tag is an estimate: images changed since they were tagged are
tagged again, and missing files are skipped. Activity reports what was tagged.") in the
`p.small`, the D55 backup box and Cancel / "Start tagging", which starts the
@@ -4650,6 +4658,51 @@ the per-section hand-off panel, was retired by S12 (#329): every section is nati
concept row's), `skipped tags add, preview, remove, save and reload (D202)`, `skipped
tags preview checks library tags only …` (workspace-system-tagging.spec) and
`suggestions omit skipped tags and say how many were hidden` (workspace-tagging.spec).
- **D203** (Auto-tagging, #431, beyond the concept): "Saved instructions" replaces the
concept's "LLM tag template" field (the mock's state 2): a `.setting-block`
(`data-setting="tag-instructions"`, `data-key="autotag_instructions"`) with the label,
"Instructions for the language-model engines. Built-in can’t be changed; duplicate it
to make your own. N of 20 saved · Built‑in is not counted.", a `role="listbox"` list
(Built-in first with a "Read-only" chip, then the saved entries; "Default for GGUF" /
"Default for HuggingFace" chips), "+ New" ("New instructions", "(2)" if taken),
"Duplicate" ("<name> copy"), "Delete" (`.danger`, disabled for Built-in), "At most 20
saved instructions." at the cap, and the editor beside the list (under it below 700px):
"Name" ("N of 60") and "Text" ("N of 4,000 characters"), read-only for Built-in with
"Built-in instructions can’t be changed. Duplicate them to make your own.", else the
entry's problem ("The name “X” is already used.", "The text must not be empty."…).
Then two concept select rows, "Default for GGUF JoyCaption" and "Default for
HuggingFace JoyCaption" ("The Tag image dialog and batches start from this.",
`data-key="autotag_default_instructions"`), and the note a deleted default leaves
("GGUF now uses Built-in."; nothing is written until Save preferences). Pinned by the
comparison's mapping (system-tagging-concept.spec: the block in the concept row's
tokens, the two rows on the concept's select row) and `saved instructions: new, edit,
per-engine default, delete, save and reload (D203)`, `a refused instructions save names
the entry and highlights the block (D203)` (workspace-system-tagging.spec).
- **D204** (Tag image and Auto-tag selected dialogs, #431, beyond the concept): for a
language-model engine a "Saved instructions" select (`#image-tag-instructions-pick`,
`#bulk-tag-instructions-pick`) sits above "Tagging instructions" (the mock's state 3),
on the concept's select field: Built-in, then the saved entries (`GET /autotag/models`
`instructions`, full text), the engine's default marked "<name> · default". It starts
at that default; a pick replaces the text. The text stays editable for the run: while
it differs from the picked entry the select shows a disabled "Edited for this run" and
a muted line under the textarea reads "Edits apply to this run only. Manage saved
instructions in System › Auto-tagging." Choosing an entry again replaces the edit.
Switching between GGUF and HF starts from the new engine's default and drops an edit;
WD14 hides both. A preset (Preview tagging review, the bulk review) shows the entry
with its text, else "Edited for this run". When the read fails the select holds only
Built-in with the client's copy (`BUILTIN_INSTRUCTIONS`, pinned equal to
`DEFAULT_TAG_TEMPLATE` by workspace-image-tagging-state.test). Request bodies are
unchanged (`options.instructions` is the textarea's text); there is no "Save as new".
The workspace no longer reads `default_prompt`. Pinned by the tagging comparison's
mapping (tagging-canonical-comparison.mjs: the select before the instructions),
`the saved instructions picker fills, edits one-off and resets per engine (D204)`
(workspace-tagging.spec) and `Auto-tag selected picks saved instructions and sends the
text shown (D204)` (workspace-tagging-bulk.spec).
- **D205** (Auto-tagging, #431; amends D134): for a language-model engine the batch
review adds a read-only "Instructions" row with the drafted engine default's name
before "Settings", which reads "Up to L tags per image"; the batch and Preview tagging
review send that entry's text. WD14 is unchanged. Pinned by `the batch review names the
language model's default instructions (D205)` (workspace-system-tagging.spec).
- **D158** (Sources, #247 D-Q4): the concept has no switch for prompts from workflows
without a PromptManager node; the maintainer chose a visible switch over a config key.
A `.setting-row` in the concept's toggle markup follows "Watch for new outputs":
@@ -4881,6 +4934,47 @@ replaced the database since the review; the plan is dropped). The job:
prompt ids only), logs the backup by its file name, and prunes safety backups to the
newest 10. With no backup the log and the status line say "No safety backup".
#### Pair category and tags repair (#550)
`pair_metadata` ("Pair category and tags", D192; `database/pair_metadata_repair.py`) fills
the empty category and the empty tag set of prompts that Pair node runs before #546
saved, from their saved pairs. It is reviewed and applied like the operations above and
follows the safety backup policy like them (no forced backup). Rules
(`docs/egm/post-3.4.1/550-design.md`):
- Considered: prompts the Pair node created as a positive: their earliest pair (lowest
id, either role, archived included) has them as the positive and its `created_at` is
within 2 s of theirs. A prompt first saved by the PromptManager node, or first as a
negative, is out of scope and not counted.
- Sources: the prompt's non-archived pairs where it is the positive; pairs with a blank
category or no tags are ignored for that field. Category values are trimmed and
case-sensitive, tag sets compared by tag id; a field fills when exactly one value is
left. A whitespace-only category counts as empty.
- Kept, per field, in this order: archived prompt, edited prompt (`updated_at` not within
2 s of `created_at`; NULL or unreadable counts as edited), pairs that disagree, a tag
set over 20 tags. A prompt can fill one field and keep the other.
- The plan lists protected rows first, then fills, then a "Record" row, within the 50
detail rows; at most 20,000 prompts per plan ("review again for the rest").
- The fingerprint covers each considered prompt's computed result (values, tag ids and
names, source pair ids), never a pair's title, notes, rating, run count or use
stamps, so a Pair run between review and apply still applies.
- The apply's writes are guarded (`category` still blank, no `prompt_tags` row) and a
guard that hits nothing rolls the whole transaction back as `plan-changed`. It never
writes `prompt_pairs`, `prompt_pair_tags`, `updated_at`, a tag or Library state.
- The job lists 200 changed rows ("Prompt 12: category “night”; tags a, b"); its
Download report lists every one (`truncated: false`, `Job.record_counted(complete=True)`).
Protected lists one row per kept prompt in the review's wording and order ("Prompt 7:
kept, pairs disagree on category: Adult, asian"), also complete in the report.
- Activity shows a row with no path as a record: its detail as plain text, never an open
or locate action, and the filter reads the detail. A maintenance job of records (every
operation but Missing-file links) says "No records in this group.", "No listed record
matches this filter." and "Filter records…" (`groupCopy`); file jobs keep "files".
Tests: `tests/test_maintenance_pair_metadata.py`, `tests/test_api_maintenance_pair_metadata.py`,
`tests/test_jobs.py`, `tests/js/workspace-system-maintenance.test.js`,
`tests/browser/workspace-system-maintenance.spec.mjs` (hook `/__test__/system/pair-metadata`).
A Python change: ComfyUI needs a restart.
The legacy restore (`POST /prompt_manager/restore`) takes a backup of any size the disks
have room for, so a real library's safety backup restores: the upload, its staged copy
and the swap (the upload twice in the temporary folder, once more plus the current
@@ -5235,16 +5329,16 @@ server-side tagging configuration, `autotag.defaults` in config.json: `model_typ
(`wd14-swinv2`, `wd14-vit`, `gguf`, `hf`), `existing` (`skip`, `merge`,
`replace_after_review`), `general_threshold` and `character_threshold` (0–1 in
steps of 0.05, #369),
`template` (non-blank, at most 4,000 characters and 16 KiB) and `suggestion_limit`
(1–100). They are host drafts (`autotag_model_type`, `autotag_existing`,
`autotag_general_threshold`, `autotag_character_threshold`, `autotag_template`,
`autotag_suggestion_limit`) saved by the one Save row through `POST
`suggestion_limit` (1–100), and since #431 `skipped_tags`, `instructions` and
`default_instructions` (below). They are host drafts (`autotag_model_type`, `autotag_existing`,
`autotag_general_threshold`, `autotag_character_threshold`, `autotag_suggestion_limit`,
`autotag_skipped_tags`, `autotag_instructions`, `autotag_default_instructions`) saved by the one Save row through `POST
/prompt_manager/settings` with `expected_revision`; a bad value is refused with a 400
naming the setting by its row label ("General-tag confidence must be between 0 and
1."), nothing is written, the Save row shows that sentence (`invalid-setting`, set by
`settingRefusal` in system.mjs only for a save that carried `autotag_*` keys; every
other section's plain 400 keeps the generic status) and the control is highlighted.
The template's limit counts code points, as Python's `len` does. A hand-edited bad value
Instruction text limits count code points, as Python's `len` does. A hand-edited bad value
falls back to its default on load, one key at a time. Every page shows the thresholds in
steps of 0.05 (the System rows, the Tag image dialogs, the legacy sliders), so a saved
threshold off that grid is refused ("General-tag confidence must be a multiple of
@@ -5255,8 +5349,42 @@ stored exactly on the grid (#369). A per-request threshold keeps its own check:
`validate_options` and starts the `autotag` job titled "Tag output images (legacy page)";
the legacy `/autotag/single` only requires a number. `GET /autotag/models` reports the
saved values (`wd14_general_threshold`, `wd14_character_threshold`, `suggestion_limit`,
`default_prompt`, `model_type`, `existing`), so the Tag image and Auto-tag selected
dialogs start from them; a preset (the bulk review, Preview tagging review) wins.
`default_prompt`, `model_type`, `existing`, and since #431 `instructions` and
`default_instructions`), so the Tag image and Auto-tag selected dialogs start from them;
a preset (the bulk review, Preview tagging review) wins.
**Saved instructions** (#431, D203, D205): `autotag.defaults.instructions` holds the
saved entries `[{id, name, text}]` in order (at most 20; `id` 12 lowercase hex made by
the client, unique, `builtin` reserved; `name` trimmed, 1–60 characters, no control
character, unique case-folded, not "Built-in"; `text` as `validate_template`), and
`autotag.defaults.default_instructions` is `{gguf, hf}`, each `builtin` or a saved id.
Built-in (`{id: "builtin", name: "Built-in", text: DEFAULT_TAG_TEMPLATE}`) is never
stored; readers put it first (`AutotagConfig.all_instructions`), so a later release's
built-in text reaches everyone who uses it. Refusals (400, the Save row shows them and
highlights the editor): "Saved instructions: at most 20 entries besides Built-in.",
"Saved instructions: entry N has an invalid id.", "… repeats the id of entry M.", "…
needs a name.", "… name must be at most 60 characters.", "… name must not contain a line
break.", "… name “X” is already used.", "Saved instructions: the name “Built-in” is
reserved.", "Saved instructions: entry N text must be …", "Default for GGUF JoyCaption
must be one of the saved instructions." (and HuggingFace). A save carrying either key
without `expected_revision` is refused ("Reload the page and try again."); a save
carrying one is paired with the other from the running configuration and both are
written, plus the old `template` key as the GGUF default's text (deleted when that
default is Built-in), so an older build reads that default as its one template. `POST`
with `autotag_template` is refused ("LLM tag template was replaced by Saved instructions.
Reload the page."); `GET` keeps returning it (the GGUF default's text, read-only) for one
release and adds `autotag_builtin_instructions` (Built-in's text, read-only). Load: each
key alone (a damaged list → Built-in only, a default naming nothing → Built-in, each with
one warning), then the in-memory migration of the old `template`: with no `instructions`
key, a template other than Built-in becomes "Custom template" (id = first 12 hex of its
SHA-256), the default of both engines unless `default_instructions` is stored; with the
key, a template matching no entry (edited on an older build) is appended as "Custom
template (n)" with one INFO line (a full list: one WARNING, nothing imported). Nothing is
written at load; the next save that carries either key writes the list.
`AutotagConfig.default_instructions_text(engine)` feeds the legacy batch and single
blank-prompt fallbacks (by the request's engine) and `default_prompt`. Tests:
`tests/test_saved_instructions.py`, `tests/test_saved_instructions_routes.py`,
`tests/js/workspace-system-instructions.test.js`.
**Skipped tags** (#431, D202): `autotag.defaults.skipped_tags`, host draft
`autotag_skipped_tags`, a list of at most 200 entries, each trimmed, 1–50 code points,