diff --git a/web/prompt_option_picker.js b/web/prompt_option_picker.js index db0e7c3..6a5b280 100644 --- a/web/prompt_option_picker.js +++ b/web/prompt_option_picker.js @@ -6,8 +6,12 @@ import { app } from "/scripts/app.js"; const NODE_NAME = "PromptOptionPicker"; +const EMPTY_SENTINEL = "\u200B"; + const COLORS = { tag: "#ff9800", + label: "#4dd0e1", + sep: "#888888", }; // --- Highlight --- @@ -22,19 +26,39 @@ const buildHighlightedHtml = (raw) => { const stripped = line.trim(); const isLast = i === lines.length - 1; - let color = DEFAULT_TEXT_COLOR; + let lineHtml; + const sep = "$:"; if (stripped === "@combine") { inCombine = true; - color = COLORS.tag; + lineHtml = `${escapeHtml(line)}`; } else if (stripped === "@end") { inCombine = false; - color = COLORS.tag; + lineHtml = `${escapeHtml(line)}`; } else if (stripped === "---" && inCombine) { - color = COLORS.tag; + lineHtml = `${escapeHtml(line)}`; + } else if (!inCombine && stripped.includes(" $: ")) { + // "label $: value" + const sep = " $: "; + const sepIdx = line.indexOf(sep); + const label = line.slice(0, sepIdx); + const value = line.slice(sepIdx + 4); + lineHtml = + `${escapeHtml(label)}` + + `${escapeHtml(sep)}` + + `${escapeHtml(value)}`; + } else if (!inCombine && stripped.startsWith("$: ")) { + // "$: value", no label + const sep = "$: "; + const value = line.slice(line.indexOf(sep) + 3); + lineHtml = + `${escapeHtml(sep)}` + + `${escapeHtml(value)}`; + } else { + lineHtml = `${escapeHtml(line)}`; } - result += `${escapeHtml(line)}`; + result += lineHtml; if (!isLast) result += "\n"; } @@ -45,16 +69,18 @@ const buildHighlightedHtml = (raw) => { const parseOptions = (raw) => { const lines = raw.split("\n"); - const result = []; - let inCombine = false; - let currentBlock = []; - let currentList = []; // Remove trailing empty line from paste artifacts if (lines.length > 1 && lines[lines.length - 1].trim() === "") { lines.pop(); } + const result = []; // { label, value } + let inCombine = false; + let currentBlock = []; + let currentList = []; + let optionCounter = 0; + for (const line of lines) { const stripped = line.trim(); @@ -85,20 +111,38 @@ const parseOptions = (raw) => { ); for (const combo of combos) { - result.push(combo || "--"); + const value = combo || ""; + result.push({ label: value || "--", value }); + optionCounter++; } } else if (stripped === "---") { if (inCombine) { currentBlock.push(currentList); currentList = []; } else { - result.push("---"); + result.push({ label: "---", value: "---" }); + optionCounter++; } } else { if (inCombine) { currentList.push(line); + } else if (stripped === "") { + result.push({ label: "--", value: EMPTY_SENTINEL }); + optionCounter++; + } else if (stripped.includes(" $: ")) { + const idx = stripped.indexOf(" $: "); + let label = stripped.slice(0, idx).trim(); + const value = stripped.slice(idx + 4).trim(); + if (!label) label = `option_${optionCounter}`; + result.push({ label, value }); + optionCounter++; + } else if (stripped.startsWith("$: ")) { + const value = stripped.slice(3).trim(); + result.push({ label: `option_${optionCounter}`, value }); + optionCounter++; } else { - result.push(line === "" ? "--" : line); + result.push({ label: stripped, value: stripped }); + optionCounter++; } } } @@ -148,13 +192,46 @@ const attachEditor = (node) => { return; } - const current = selectedWidget.value; - selectedWidget.options.values = options; - selectedWidget.value = options.includes(current) ? current : options[0]; + // Build label map, handle duplicate labels + const labelMap = new Map(); + for (const { label, value } of options) { + let uniqueLabel = label; + let suffix = 1; + while (labelMap.has(uniqueLabel)) { + uniqueLabel = `${label} (${suffix++})`; + } + labelMap.set(uniqueLabel, value); + } + + node._labelMap = labelMap; + const labels = [...labelMap.keys()]; + + // Preserve selection by raw value + const currentRaw = selectedWidget.value; + const currentLabel = currentRaw + ? [...labelMap.entries()].find(([, v]) => v === currentRaw)?.[0] + : null; + + selectedWidget.options.values = labels; + const targetLabel = + currentLabel && labels.includes(currentLabel) + ? currentLabel + : labels[0]; + + // Store raw value in selectedWidget + selectedWidget.value = labelMap.get(targetLabel) ?? targetLabel; if (node.graph) node.graph.setDirtyCanvas(true, true); }; + hookWidget(selectedWidget, (label) => { + if (node._labelMap) { + selectedWidget.value = node._labelMap.get(label) ?? label; + } + refreshDropdown(); + renderColored(); + }); + hookWidget(optionsWidget, () => { refreshDropdown(); renderColored();