From e9a1510db01b358b0cd971cfb8ef3181d85f183a Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sat, 30 May 2026 11:55:17 +0300 Subject: [PATCH] Add "Loved it" modifier to rating picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any rating can now be combined with a "Loved it" toggle. The suffix |loved is appended to the rating string and parsed in normalize_refiner_v2_rating. When active: reward boosted to max(base, 0.85), axis pressure softened 50%, conditioning direction delta raised to 0.45, preferred context applied at 0.30 base_delta. Disabled for Perfect (redundant) and -Just forget it-. Button label shows ♥ when modifier is active. Co-Authored-By: Claude Sonnet 4.6 --- conditioning.py | 22 +++++++++++++++++- web/funpack_rating_picker.js | 45 +++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/conditioning.py b/conditioning.py index 7ebf18e7..c2d49aa8 100644 --- a/conditioning.py +++ b/conditioning.py @@ -5719,10 +5719,18 @@ V2_RATING_ALIASES = { def normalize_refiner_v2_rating(value): + loved_modifier = False + if isinstance(value, str) and value.endswith("|loved"): + value = value[:-6] + loved_modifier = True if isinstance(value, str): cleaned = V2_RATING_ALIASES.get(value.strip(), value.strip()) if cleaned in V2_RATING_PROFILES: - return dict(V2_RATING_PROFILES[cleaned], label=cleaned) + profile = dict(V2_RATING_PROFILES[cleaned], label=cleaned) + if loved_modifier: + profile["loved_modifier"] = True + profile["reward"] = max(float(profile.get("reward", 0.0)), 0.85) + return profile try: value = int(float(cleaned)) except (TypeError, ValueError): @@ -8210,6 +8218,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): elif rating_key == "nailed_it": delta = -0.10 entry["forgiven_count"] = int(entry.get("forgiven_count", 0)) + 1 + elif rating_profile.get("loved_modifier"): + delta *= 0.5 # soften axis pressure — quality was good, still try to fix it elif rating_key == "loved_it": continue # axis-blind quality endorsement — don't learn prompt adherence elif rating_key == "awful": @@ -8421,6 +8431,9 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): elif rating_profile.get("key") == "nailed_it": positive_axes = {"action", "details"} base_delta = 0.65 + elif rating_profile.get("loved_modifier"): + positive_axes = {"action", "details"} + base_delta = 0.30 elif rating_profile.get("key") == "loved_it": return "Preferred context: skipped for axis-blind quality endorsement." else: @@ -8612,6 +8625,13 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): else: delta = 0.0 count_category_evidence = False + elif rating_profile.get("loved_modifier"): + delta = 0.45 * kind_scale + entry["liked_count"] = int(entry.get("liked_count", 0)) + 1 + rating_evidence["liked"] = int(rating_evidence.get("liked", 0)) + 1 + for category, score in effective_before.items(): + if float(score) >= 0.28: + weights[category] = float(weights.get(category, 0.0)) + 0.10 * kind_scale elif rating_profile.get("key") == "nailed_it": delta = 0.35 * kind_scale entry["liked_count"] = int(entry.get("liked_count", 0)) + 1 diff --git a/web/funpack_rating_picker.js b/web/funpack_rating_picker.js index 69e8019b..2a0d1044 100644 --- a/web/funpack_rating_picker.js +++ b/web/funpack_rating_picker.js @@ -108,7 +108,16 @@ function injectStyles() { .fp-picker-opt-reward { font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums; } .fp-picker-opt-hint { font-size: 9px; color: #888; line-height: 1.3; } .fp-picker-opt:hover .fp-picker-opt-hint { color: #aaa; } - .fp-picker-nuclear { padding: 8px 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 5px; } + .fp-picker-nuclear { padding: 8px 10px 4px; display: grid; grid-template-columns: 1fr 1fr; gap: 5px; } + .fp-picker-loved-row { padding: 6px 10px 8px; border-top: 1px solid rgba(255,255,255,0.06); } + .fp-picker-loved-toggle { + width: 100%; padding: 7px 10px; border-radius: 6px; cursor: pointer; + font: 11px sans-serif; font-weight: 600; text-align: left; + background: rgba(255,255,255,0.04); border: 1px solid rgba(255,120,180,0.25); color: #aaa; + transition: background 0.1s, border-color 0.1s, color 0.1s; + } + .fp-picker-loved-toggle:hover { background: rgba(255,120,180,0.10); border-color: rgba(255,120,180,0.5); color: #eee; } + .fp-picker-loved-toggle.active { background: rgba(255,120,180,0.18); border-color: rgba(255,120,180,0.75); color: #ffb3d4; } `; document.head.append(s); } @@ -157,6 +166,8 @@ function makeOption(r, currentValue, accent, onPick) { return btn; } +const NO_LOVED_LABELS = new Set(["Perfect", FORGET_LABEL]); + function openPicker(ratingWidget, event, onPick) { closePicker(); injectStyles(); @@ -176,7 +187,13 @@ function openPicker(ratingWidget, event, onPick) { header.append(closeBtn); picker.append(header); - const current = ratingWidget.value; + const rawCurrent = ratingWidget.value || ""; + const currentLoved = rawCurrent.endsWith("|loved"); + const currentBase = currentLoved ? rawCurrent.slice(0, -6) : rawCurrent; + + let lovedOn = currentLoved; + + const buildLabel = (base) => NO_LOVED_LABELS.has(base) ? base : (lovedOn ? base + "|loved" : base); // Category sections for (const cat of CATEGORIES) { @@ -191,7 +208,7 @@ function openPicker(ratingWidget, event, onPick) { const grid = document.createElement("div"); grid.className = cat.id === "positive" ? "fp-picker-positive-grid" : "fp-picker-grid"; - for (const r of cat.ratings) grid.append(makeOption(r, current, cat.accent, onPick)); + for (const r of cat.ratings) grid.append(makeOption(r, currentBase, cat.accent, (base) => onPick(buildLabel(base)))); section.append(grid); picker.append(section); } @@ -200,9 +217,22 @@ function openPicker(ratingWidget, event, onPick) { const nuclearSection = document.createElement("div"); nuclearSection.className = "fp-picker-nuclear"; nuclearSection.style.setProperty("--accent", "140,100,100"); - for (const r of NUCLEAR) nuclearSection.append(makeOption(r, current, "140,100,100", onPick)); + for (const r of NUCLEAR) nuclearSection.append(makeOption(r, currentBase, "140,100,100", (base) => onPick(buildLabel(base)))); picker.append(nuclearSection); + // Loved it toggle + const lovedRow = document.createElement("div"); + lovedRow.className = "fp-picker-loved-row"; + const lovedToggle = document.createElement("button"); + lovedToggle.className = "fp-picker-loved-toggle" + (lovedOn ? " active" : ""); + lovedToggle.textContent = "♥ Loved it — great output regardless of prompt adherence"; + lovedToggle.addEventListener("click", () => { + lovedOn = !lovedOn; + lovedToggle.classList.toggle("active", lovedOn); + }); + lovedRow.append(lovedToggle); + picker.append(lovedRow); + document.body.append(picker); // Position at mouse, clamped to viewport @@ -250,8 +280,10 @@ function setupRatingPicker(node) { hideWidget(ratingWidget); const getLabel = () => { - const v = ratingWidget.value; - return (!v || v === FORGET_LABEL) ? "Waiting for rating..." : `Rating: ${v}`; + const v = ratingWidget.value || ""; + if (!v || v === FORGET_LABEL) return "Waiting for rating..."; + if (v.endsWith("|loved")) return `Rating: ${v.slice(0, -6)} ♥`; + return `Rating: ${v}`; }; node.widgets = (node.widgets || []).filter((w) => w.__fpPickerWidget !== true); @@ -280,6 +312,7 @@ function resetRating(node) { node.__fpPickerWidgetRef.name = "Waiting for rating..."; node.setDirtyCanvas?.(true, true); } + closePicker(); } // ─── Extension ────────────────────────────────────────────────────────────────