Files

201 lines
8.2 KiB
JavaScript

// prompt_machine_frontend.js
import { app } from "../../../scripts/app.js";
const PROMPT_MACHINE_TOOLTIPS = {
subject: "Core hero or subject and physical details (age, hair, eyes, outfit, accessories).\nExample: 'A young female astronomer with messy raven hair and grey eyes, wearing a fitted leather jacket.'",
pose_and_expression: "Body posture, facial expression, action, and primary gestures.\nExample: 'Leaning over an ancient star map with a subtle smile, holding a brass compass in her right hand.'",
secondary_subject: "Supporting characters, companion animals, or secondary focal objects.\nExample: 'A sleek white robotic cat sitting on the desk beside her, watching the holograms.'",
background_and_environment: "Immediate physical surroundings, wider environment, and background setting.\nExample: 'Inside a high-tech glass dome observatory with scattered parchment maps, overlooking a star-filled galaxy.'",
lighting: "Lighting sources, direction, intensity, reflections, shadows, and mood.\nExample: 'Soft cyan holographic glow illuminating her face, warm golden sunlight streaming through the side window.'",
style: "Visual medium, photographic style, aesthetic textures, and dominant color palette.\nExample: 'Cinematic 35mm film photography, fine grain, shallow depth of field. Deep navy blues and warm amber tones.'",
creative_hook: "One surreal, impossible, or striking visual concept.\nExample: 'Constellations floating like tiny glowing fireflies in the air inside the room.'",
technical: "Camera equipment, lens specs, shot type, and framing (Optional).\nExample: 'Shot on 85mm f/1.4 lens, eye-level medium close-up, sharp focus on subject, soft background bokeh.'",
json_output: "Output structured JSON mapping instead of a formatted text paragraph."
};
function setupPromptMachine(node) {
if (!node.widgets) return;
node.widgets.forEach(w => {
if (PROMPT_MACHINE_TOOLTIPS[w.name]) {
w.tooltip = PROMPT_MACHINE_TOOLTIPS[w.name];
if (w.options) {
w.options.tooltip = PROMPT_MACHINE_TOOLTIPS[w.name];
}
const updateInputEl = () => {
if (w.inputEl) {
w.inputEl.title = PROMPT_MACHINE_TOOLTIPS[w.name];
const firstLine = PROMPT_MACHINE_TOOLTIPS[w.name].split('\n')[0];
if (!w.inputEl.placeholder) {
w.inputEl.placeholder = firstLine;
}
w.inputEl.readOnly = false;
w.inputEl.disabled = false;
}
};
updateInputEl();
// Retry if inputEl creates asynchronously
if (!w.inputEl) {
setTimeout(updateInputEl, 100);
setTimeout(updateInputEl, 500);
}
}
});
}
function setupPromptStyleMachine(node) {
if (node._sata_style_initialized) return;
const csvWidget = node.widgets?.find(w => w.name === "csv_file");
let nameWidget = node.widgets?.find(w => w.name === "name");
if (!csvWidget || !nameWidget) {
return;
}
node._sata_style_initialized = true;
const initialSavedName = nameWidget.value || "None";
// Convert widget to combo dropdown gracefully if it's not already
if (nameWidget.type !== "combo") {
const index = node.widgets.indexOf(nameWidget);
if (index !== -1) {
node.widgets.splice(index, 1);
nameWidget = node.addWidget("combo", "name", initialSavedName, () => { }, {
values: [initialSavedName !== "None" ? initialSavedName : "None"]
});
const newIdx = node.widgets.indexOf(nameWidget);
if (newIdx !== -1 && newIdx !== index) {
node.widgets.splice(newIdx, 1);
node.widgets.splice(index, 0, nameWidget);
}
}
}
if (!nameWidget.options) {
nameWidget.options = { values: [initialSavedName] };
}
let namesConfig = [];
// Fetch names from backend for a given CSV
async function loadNames(csvValue, preferredName) {
if (!csvValue || csvValue === "None") return;
try {
const resp = await fetch(`/sata/prompt_machine/names?csv=${encodeURIComponent(csvValue)}`);
const data = await resp.json();
namesConfig = (data.names && data.names.length > 0) ? data.names : ["None"];
refreshNames(preferredName);
} catch (err) {
console.error("[PromptMachineFrontend] Failed to load names:", err);
}
}
// Refresh dropdown with available names while preserving target selection
function refreshNames(preferredName) {
const available = namesConfig.length > 0 ? namesConfig : ["None"];
nameWidget.options.values = available;
const targetVal = preferredName !== undefined ? preferredName : nameWidget.value;
if (available.includes(targetVal)) {
nameWidget.value = targetVal;
} else {
nameWidget.value = available[0] || "None";
}
// Force update previews for current name
updatePreviews(nameWidget.value);
if (app.graph) {
if (app.graph.setDirtyCanvas) app.graph.setDirtyCanvas(true, true);
if (app.graph.change) app.graph.change();
}
}
// Hook CSV change (user triggered)
const oldCsvCallback = csvWidget.callback;
csvWidget.callback = function (value) {
if (oldCsvCallback) oldCsvCallback.apply(this, arguments);
loadNames(value); // refresh without preferred name (defaults to first available)
};
// --- Preview Logic ---
let previewEl = node.widgets?.find(w => w.name === "preview_text")?.element;
if (!previewEl) {
previewEl = document.createElement("div");
Object.assign(previewEl.style, {
marginTop: "10px",
padding: "8px",
background: "rgba(0,0,0,0.4)",
borderRadius: "4px",
fontSize: "14px",
lineHeight: "1.5",
color: "#ddd",
wordBreak: "break-word",
maxHeight: "400px",
overflowY: "auto"
});
node.addDOMWidget("preview_text", "html", previewEl, {
getValue: () => previewEl.innerHTML,
setValue: (v) => { previewEl.innerHTML = v; },
serialize: false
});
}
async function updatePreviews(name) {
const csv = csvWidget.value;
if (!csv || !name || name === "None") {
previewEl.innerHTML = "<i>No preview available</i>";
return;
}
try {
const resp = await fetch(`/sata/prompt_machine/get?csv=${encodeURIComponent(csv)}&name=${encodeURIComponent(name)}`);
const data = await resp.json();
let html = "";
if (data.positive) html += `${data.positive}`;
previewEl.innerHTML = html || "<i>No preview available</i>";
if (node.computeSize && node.setSize) {
node.setSize(node.computeSize());
}
if (app.graph) {
if (app.graph.setDirtyCanvas) app.graph.setDirtyCanvas(true, true);
if (app.graph.change) app.graph.change();
}
} catch (err) {
console.error("[PromptMachineFrontend] Failed to load preview:", err);
}
}
// Hook Name change
const oldNameCallback = nameWidget.callback;
nameWidget.callback = function (value) {
if (oldNameCallback) oldNameCallback.apply(this, arguments);
updatePreviews(value);
};
// Initial sync preserving saved value
loadNames(csvWidget.value, initialSavedName);
}
app.registerExtension({
name: "SATA_UtilityNode.PromptMachineFrontend",
async nodeCreated(node) {
if (node.comfyClass === "Prompt_Machine") {
setupPromptMachine(node);
} else if (node.comfyClass === "Prompt_Style_Machine") {
setupPromptStyleMachine(node);
}
},
async loadedGraphNode(node) {
if (node.comfyClass === "Prompt_Machine") {
setupPromptMachine(node);
} else if (node.comfyClass === "Prompt_Style_Machine") {
setupPromptStyleMachine(node);
}
}
});