feat: add meta hints user marks for metadata heuristic override

Users can now right-click nodes and assign meta hints
(primary_model, primary_sampler, positive_prompt,
negative_prompt) to override the metadata processor's
heuristic inference.

- Store extra_data from the API request so workflow node
  properties (including lm_marker_role) are accessible
  during metadata processing.
- _get_user_marks scans extra_data.extra_pnginfo.workflow
  for meta_* marks, falling back to prompt.original_prompt.
- extract_generation_params checks user marks before
  heuristic inference for sampler, model, and prompts.
- Warn on duplicate marks or invalid marked nodes.
This commit is contained in:
Will Miao
2026-07-25 22:13:52 +08:00
parent f34c02756d
commit e6dc169a05
4 changed files with 172 additions and 23 deletions

View File

@@ -7,12 +7,16 @@ import { app } from "../../scripts/app.js";
// Roles are stored in ``node.properties.lm_marker_role`` and automatically
// persist with the workflow JSON.
//
// Two categories:
// send_* consumed by the standalone UI's "Send to Workflow" feature
// meta_* consumed by the metadata processor to override heuristic inference
//
// The workflow registry reads these markers and makes them available to the
// standalone UI (e.g. ``sendEmbeddingToWorkflow`` also considers nodes marked
// as ``send_prompt_target``).
// =============================================================================
const ROLES = {
const SEND_ROLES = {
send_prompt_target: {
label: "Send Prompt Target",
emoji: "\uD83D\uDCDD",
@@ -23,6 +27,28 @@ const ROLES = {
},
};
const META_ROLES = {
meta_primary_model: {
label: "Meta hints: Primary Model",
emoji: "\uD83D\uDCA1",
},
meta_primary_sampler: {
label: "Meta hints: Primary Sampler",
emoji: "\uD83D\uDCA1",
},
meta_positive_prompt: {
label: "Meta hints: Positive Prompt",
emoji: "\uD83D\uDCA1",
},
meta_negative_prompt: {
label: "Meta hints: Negative Prompt",
emoji: "\uD83D\uDCA1",
},
};
// Flat lookup for setMarker / getMarker / clearMarker
const ROLES = { ...SEND_ROLES, ...META_ROLES };
// ---- Helpers ----------------------------------------------------------------
function getMarker(node) {
@@ -54,7 +80,7 @@ function clearMarker(node) {
// Restore original title: prefer stripping emoji from current title
// (captures user renames after marking), fall back to saved original.
const cleaned = node.title?.replace(
/^(\u2709\uFE0F?|\u2699\uFE0F?|\uD83D\uDCDD|\uD83C\uDF9B\uFE0F?|\uD83D\uDD27)\s*/,
/^(\u2709\uFE0F?|\u2699\uFE0F?|\uD83D\uDCDD|\uD83C\uDF9B\uFE0F?|\uD83D\uDD27|\uD83D\uDCA1)\s*/,
''
);
if (cleaned && cleaned !== node.title) {
@@ -84,16 +110,23 @@ function buildSubmenuOptions(node) {
const currentRole = getMarker(node);
const options = [];
for (const [key, def] of Object.entries(ROLES)) {
const isActive = currentRole === key;
options.push({
content: `${isActive ? "\u2713 " : ""}${def.label}`,
disabled: isActive,
callback: () => setMarker(node, key),
});
}
const buildGroup = (roles) => {
for (const [key, def] of Object.entries(roles)) {
const isActive = currentRole === key;
options.push({
content: `${isActive ? "\u2713 " : ""}${def.label}`,
disabled: isActive,
callback: () => setMarker(node, key),
});
}
};
buildGroup(SEND_ROLES);
options.push(null); // separator
buildGroup(META_ROLES);
if (currentRole) {
options.push(null); // separator
options.push({
content: "Clear marker",
callback: () => clearMarker(node),