From 7fe4d12312903dcce186c8274c4acc0333429c58 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:34:23 +0300 Subject: [PATCH] feat(ui): Composer Enhance tab edits the prompt enhancer node's settings Co-Authored-By: Claude Sonnet 5.5 --- app/ui/features/composer/enhance.js | 53 +++++++++++++++++++++++++++++ app/ui/features/composer/index.js | 5 +-- app/ui/shell/api.js | 2 ++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 app/ui/features/composer/enhance.js diff --git a/app/ui/features/composer/enhance.js b/app/ui/features/composer/enhance.js new file mode 100644 index 00000000..15debe5f --- /dev/null +++ b/app/ui/features/composer/enhance.js @@ -0,0 +1,53 @@ +// Enhance: a language model rewrites the finished prompt before it is encoded. These are the inputs of the pipeline's +// enhancer node, edited through the shared pipeline state; off, or on failure, the prompt runs as typed. +import { composer as c } from "../../composer/composer.js"; + +const NODE = "FunPackEnhancePrompt"; +const DIALS = [["temperature", 0.7, 0, 2, 0.05], ["top_p", 0.92, 0, 1, 0.01], ["top_k", 50, 1, 1000, 1], ["min_p", 0.05, 0, 1, 0.01], ["repetition_penalty", 1.3, 1, 2, 0.05], ["presence_penalty", 0, 0, 2, 0.05]]; + +export function enhance(app, own) { + const ps = app.pipeline, page = c.region.stack({ gap: "sm" }); + let defaults = null, last = null, alive = true; + own(() => { alive = false; }); + const slot = () => (ps.slots() || []).find((s) => s.node === NODE) || null; + const set = async (name, value) => { const sl = slot(); if (sl) { await ps.save({ inputs: { [sl.id]: { [name]: value } } }); draw(); } }; + const field = (label, control, hint) => c.field.default({ label, control, hint }); + const num = (label, v, lo, hi, step, name) => field(label, c.number.md({ label, value: v, min: lo, max: hi, step, onChange: (x) => set(name, x) })); + + function readout() { + if (!last) return [c.label.section({ text: "Last run" }), c.hint.default({ text: "Nothing yet. The rewrite of a run shows here." })]; + const part = (title, body) => (body ? c.collapsible.default({ label: title, body: c.code.block({ text: body, label: title }) }) : null); + return [c.label.section({ text: "Last run" }), c.hint.default({ text: last.status || "" }), part("Before", last.before), + c.field.default({ label: "After", control: c.text.sm({ text: last.after || "" }) }), part("Model's thinking", last.thinking), part("Sent to the model (chat run)", last.sent)].filter(Boolean); + } + function draw() { + if (!alive) return; + const sl = slot(); + if (!sl) return page.set([c.hint.default({ text: "The open pipeline has no prompt enhancer. Pick a preset that has one, or add the “FunPack Enhance Prompt” node in Models & Pipeline." })]); + const v = sl.inputs || {}, val = (n, d) => (v[n] === undefined ? d : v[n]); + const on = val("enabled", false), greedy = val("greedy", false); + page.set([ + c.label.section({ text: "Prompt enhancer" }), + c.hint.default({ text: "A language model rewrites the finished prompt before it is encoded. Off, or if it fails, your prompt is used as typed. (word:1.5) and [a@2-4] markup does not survive a rewrite." }), + c.toggle.default({ label: "Enhance prompt", checked: on, onChange: (x) => set("enabled", x) }), + ...(!on ? readout() : [ + field("Instructions", c.textarea.md({ label: "Instructions", rows: 6, value: val("instructions", ""), placeholder: defaults ? defaults.instructions : "(built-in instructions)", onCommit: (x) => set("instructions", x) }), "Empty uses the built-in instructions."), + num("Max tokens", val("max_length", 400), 32, 4096, 8, "max_length"), + c.toggle.default({ label: "Use the picture", hint: "Sends the scene's picture to the model along with the prompt.", checked: val("use_image", true), onChange: (x) => set("use_image", x) }), + c.toggle.default({ label: "Thinking", hint: "Let a thinking model reason first. Its reasoning eats the token budget.", checked: val("thinking", false), onChange: (x) => set("thinking", x) }), + c.toggle.default({ label: "Greedy (always the likeliest word)", checked: greedy, onChange: (x) => set("greedy", x) }), + ...(greedy ? [] : DIALS.map(([n, d, lo, hi, st]) => num(n, val(n, d), lo, hi, st, n))), + num("Seed", val("seed", 0), 0, 2 ** 53, 1, "seed"), + c.hint.default({ text: "Seed 0 = a fresh rewrite every Generate; any other number repeats the same rewrite." }), + c.label.section({ text: "Reference" }), + field("Lorebook / shortcut files", c.textarea.md({ label: "Lorebook files", rows: 2, value: val("lorebooks", ""), placeholder: "one path per line", onCommit: (x) => set("lorebooks", x) }), "SillyTavern lorebooks or FunPack shortcut files, on the machine running ComfyUI."), + field("Reference intro", c.textarea.md({ label: "Reference intro", rows: 2, value: val("reference_intro", ""), placeholder: defaults ? defaults.reference_intro : "", onCommit: (x) => set("reference_intro", x) })), + ...readout()]), + ]); + } + draw(); + ps.ensureLoaded().then(draw); + app.api.enhancerDefaults().then((d) => { defaults = d; draw(); }).catch(() => {}); + app.api.enhancerRuns().then((r) => { last = (r.runs || []).slice(-1)[0] || null; draw(); }).catch(() => {}); + return page; +} diff --git a/app/ui/features/composer/index.js b/app/ui/features/composer/index.js index 01ad347f..78db514f 100644 --- a/app/ui/features/composer/index.js +++ b/app/ui/features/composer/index.js @@ -3,6 +3,7 @@ import { composer as c } from "../../composer/composer.js"; import { applyStory, joinStory } from "./story.js"; import { shortcuts } from "./shortcuts.js"; import { cuts } from "./cuts.js"; +import { enhance } from "./enhance.js"; const TABS = [{ value: "story", label: "Story" }, { value: "shortcuts", label: "Shortcuts" }, { value: "cuts", label: "Cuts" }, { value: "enhance", label: "Enhance" }, { value: "chat", label: "Chat" }]; const inert = (label, tone = "ghost") => c.button.sm({ label, tone, disabled: true }); @@ -40,12 +41,12 @@ const story = (app, own) => { }; const sheets = { story, shortcuts, cuts, - enhance: () => later("Rewrite a prompt with a language model."), chat: () => later("Talk a scene through with the enhancer.") }; + enhance, chat: () => later("Talk a scene through with the enhancer.") }; export default { id: "composer", mount: "timeline.status", - needs: ["project", "api"], + needs: ["project", "api", "pipeline"], setup({ host, app }) { let win = null, owned = []; const cleanup = () => { owned.forEach((f) => f()); owned = []; }; diff --git a/app/ui/shell/api.js b/app/ui/shell/api.js index 34ee5854..42f6ec43 100644 --- a/app/ui/shell/api.js +++ b/app/ui/shell/api.js @@ -33,6 +33,8 @@ export const api = { deleteShortcut: (name) => call("DELETE", `/api/shortcuts/${encodeURIComponent(name)}`), revolver: () => call("GET", "/api/shortcuts/revolver"), setRevolver: (enabled, random) => call("POST", "/api/shortcuts/revolver", { enabled, random }), + enhancerDefaults: () => call("GET", "/api/m/conditioning_prompt_enhancer/defaults"), + enhancerRuns: () => call("GET", "/api/m/conditioning_prompt_enhancer/runs"), packs: () => call("GET", "/api/packs"), pack: (action, body) => call("POST", `/api/packs/${action}`, body || {}), git: (action, body) => call("POST", `/api/git/${action}`, body || {}),