diff --git a/_xbr_pro_block.js b/_xbr_pro_block.js index 2023b34..74ac741 100644 --- a/_xbr_pro_block.js +++ b/_xbr_pro_block.js @@ -53,7 +53,7 @@ const XBR_INFERENCE_MODES_ = XBR_INFERENCE_MODES; function xbrLlmDefaults() { return { model: { model: "", mmproj: "None", chat_handler: "None", n_ctx: 8192, vram_limit: -1, image_min_tokens: 0, image_max_tokens: 0 }, - run: { inference_mode: "one by one", max_frames: 24, max_size: 256, seed: 0, seed_control: "randomize", force_offload: false, save_states: false }, + run: { inference_mode: "one by one", max_frames: 24, max_size: 256, seed: 0, seed_control: "randomize", force_offload: false, save_states: false, strip_thinking: true }, params: { max_tokens: 6144, top_k: 40, top_p: 0.9, min_p: 0.05, typical_p: 1.0, temperature: 0.6, repeat_penalty: 1.12, frequency_penalty: 0.0, present_penalty: 0.0, @@ -83,6 +83,7 @@ function xbrParseLlm(raw) { cfg.run.seed_control = xbrPick(r.seed_control, XBR_SEED_MODES, cfg.run.seed_control); cfg.run.force_offload = xbrFlag(r.force_offload, cfg.run.force_offload); cfg.run.save_states = xbrFlag(r.save_states, cfg.run.save_states); + cfg.run.strip_thinking = xbrFlag(r.strip_thinking, cfg.run.strip_thinking); // 模型输出思考过程时自动过滤 const p = (d.params && typeof d.params === "object") ? d.params : {}; cfg.params.max_tokens = xbrInt(p.max_tokens, cfg.params.max_tokens, 0, 262144); @@ -131,7 +132,7 @@ function xbrSaveJson(node, patch) { return cur; } -/* ── 预设设定词(三视图 / 四视图 / 背景纯透明的设定文本)───────────── +/* ── 预设设定词(三视图 / 四视图 / 五视图 / 背景纯透明的设定文本)───────────── * · 用户改过的按「模式|语言」存进节点(基础面板的 preset_texts 存档); * · 弹窗读「存档 → 默认」,提交时同步写 widget + 存档。 */ function xbrPresetStore(node) { @@ -142,6 +143,28 @@ function xbrPresetTextFor(node, mode, lang) { return (t && String(t).trim()) ? t : (defaultPresetOf(mode, lang) || ""); } +/* ── 预设选项按「输出语言」过滤 ─────────────────────────────── + * 预设名的语言标签形如 "Z-Image Turbo [ZH]" / "Normal - 描述 [EN]"; + * 输出语言选中文 → 增强预设 / 反推预设 只列 [ZH] 项,选英文 → 只列 [EN] 项。 */ +function xbrLangTagOf(v) { + const m = String(v == null ? "" : v).match(/[\[[](ZH|EN)[\]]]\s*$/i); + return m ? m[1].toUpperCase() : ""; +} +function xbrFilterByLang(opts, lang) { + const want = xbrLangTagOf(lang); + if (!want) return opts.slice(); + const hit = opts.filter((o) => xbrLangTagOf(o) === want); + return hit.length ? hit : opts.slice(); // 全无语言标签(老预设)→ 全量兜底 +} +/** 语言变了:优先换到「同名的另一语言项」,否则取该语言第一项 */ +function xbrSnapPreset(opts, cur, lang) { + const list = xbrFilterByLang(opts, lang); + if (list.includes(cur)) return cur; + const base = String(cur == null ? "" : cur).replace(/\s*[\[[](ZH|EN)[\]]]\s*$/i, "").trim(); + const same = list.find((o) => String(o).replace(/\s*[\[[](ZH|EN)[\]]]\s*$/i, "").trim() === base); + return same || list[0] || cur; +} + /* ── 在线 API(存 ComfyUI user 目录,Key 不进工作流)──────── */ async function xbrApiGet() { const resp = await api.fetchApi(XBR_API_ENDPOINT); @@ -338,8 +361,11 @@ function xbrRenderLlm(body, node, draft, ctx) { draft.lang = v; // 换语言 → 未在弹窗里改过设定词时,跟着取该语言的「存档 → 默认」 if (!draft.presetTouched) draft.presetText = xbrPresetTextFor(node, draft.mode, v); + // 语言决定预设可选范围:增强预设 / 反推预设 自动换到同语言的同名项(没有则取该语言第一项) + draft.preset = xbrSnapPreset(xbrWidgetOptions(node, "preset"), draft.preset, v); + draft.task_preset = xbrSnapPreset(xbrWidgetOptions(node, "task_preset"), draft.task_preset, v); }))); - body.append(xbrHint("本项是唯一的语言设置:① 元素词表 / 预设句按哪种语言加载 ② LLM 反推最终输出的提示词是中文还是英文。其他地方不再有语言选项。")); + body.append(xbrHint("本项是唯一的语言设置:① 词表 / 设定词 / 预设句按哪种语言加载 ② 增强预设与反推预设只列该语言的选项 ③ 最终输出的提示词语言。")); body.append(makeSectionTitle("提示词增强反推(总开关在节点表面的「启用 LLM 反推」)")); @@ -352,7 +378,7 @@ function xbrRenderLlm(body, node, draft, ctx) { const llm = s.model; const models = lists.model.length ? lists.model : (llm.model ? [llm.model] : []); if (!llm.model && models.length) llm.model = models[0]; - body.append(field("强制卸载", checkboxControl(s.run.force_offload, "LLM 用完即卸载(释放显存)", (v) => { s.run.force_offload = v; }))); + body.append(field("强制卸载", checkboxControl(s.run.force_offload, "LLM 用完即卸载,释放显存", (v) => { s.run.force_offload = v; }))); if (models.length) body.append(field("模型", selectControl(models, llm.model, (v) => { llm.model = v; }))); else body.append(field("模型", xbrTextControl(llm.model, "未找到本地 LLM 模型(.gguf 放到 models/LLM)", (v) => { llm.model = v; }))); body.append(field("视觉模块 mmproj", selectControl(lists.mmproj.length ? lists.mmproj : ["None"], llm.mmproj, (v) => { llm.mmproj = v; }))); @@ -382,7 +408,7 @@ function xbrRenderLlm(body, node, draft, ctx) { const a = draft.api || (draft.api = { ...XBR_API_DEFAULTS }); body.append(field("服务商", selectControl(ctx.providers, a.provider, (v) => { a.provider = v; }))); body.append(field("模型", xbrTextControl(a.model, XBR_API_DEFAULTS.model, (v) => { a.model = v; }))); - body.append(field("API Key", xbrPasswordControl(a.api_key, "sk-…(保存在本地,不写入工作流)", (v) => { a.api_key = v; }))); + body.append(field("API Key", xbrPasswordControl(a.api_key, "sk-… 保存在本地,不写入工作流", (v) => { a.api_key = v; }))); body.append(field("Base URL", xbrTextControl(a.base_url, XBR_API_DEFAULTS.base_url, (v) => { a.base_url = v; }))); body.append(field("temperature", xbrNumberControl(a.temperature, { min: 0, max: 2, step: 0.01 }, (v) => { a.temperature = v; }))); body.append(field("max_tokens", xbrNumberControl(a.max_tokens, { min: 1, max: 262144, step: 1 }, (v) => { a.max_tokens = Math.round(v); }))); @@ -398,8 +424,13 @@ function xbrRenderLlm(body, node, draft, ctx) { body.append(field("推理模式", selectControl(XBR_INFERENCE_MODES, s.run.inference_mode, (v) => { s.run.inference_mode = v; }))); body.append(field("最大帧数", xbrNumberControl(s.run.max_frames, { min: 2, max: 1024, step: 1 }, (v) => { s.run.max_frames = Math.round(v); }))); body.append(field("最大尺寸", xbrNumberControl(s.run.max_size, { min: 128, max: 16384, step: 64 }, (v) => { s.run.max_size = Math.round(v); }))); - body.append(field("保存对话状态", checkboxControl(s.run.save_states, "在内存中保留本次对话上下文(多轮连续反推)", (v) => { s.run.save_states = v; }))); + body.append(field("保存对话状态", checkboxControl(s.run.save_states, "在内存中保留本次对话上下文,多轮连续反推", (v) => { s.run.save_states = v; }))); body.append(field("状态 UID", xbrNumberControl(s.params.state_uid, { min: -1, max: 999999, step: 1 }, (v) => { s.params.state_uid = Math.round(v); }))); + body.append(field("🧠 过滤思考过程", checkboxControl(s.run.strip_thinking, + "模型把「思考过程 / 推理段 / 工作流程」一起输出时,自动只保留最终提示词", (v) => { s.run.strip_thinking = v; }))); + body.append(el("div", "font-size:11px;color:#888;line-height:1.65;margin:2px 0 8px;", + "开启后=① 系统提示词末尾追加「只输出最终提示词」硬规则 ② 仍漏出思考时自动剔除思考标签、" + + "Final Output / 最终输出 标记之前的推理段与代码围栏(拿不到内容则原文保留)。")); body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "提示词正文在节点上的提示词框里编辑;「📝 提示词」端口接线后该框锁定。")); } @@ -417,7 +448,7 @@ function xbrRenderPreset(body, node, draft, ctx) { draft.presetText = xbrPresetTextFor(node, v, draft.lang); ctx.rerender(); // 有/无设定词的模式之间切换 → 重画「设定词」区 }))); - body.append(xbrHint("常规文生图 = 只输出正文;人物三视图 / 人物四视图 / 背景纯透明 = 最终提示词最顶端自动加上该模式的设定词。")); + body.append(xbrHint("常规文生图 = 只输出正文;人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 = 最终提示词最顶端自动加上该模式的设定词。")); // ── 设定词(预设模式对应的设定文本;只在弹窗里编辑,节点表面不显示)── const modeDef = defaultPresetOf(draft.mode, draft.lang); @@ -426,7 +457,7 @@ function xbrRenderPreset(body, node, draft, ctx) { if (!String(draft.presetText || "").trim()) draft.presetText = modeDef; const taP = textareaControl(draft.presetText, (v) => { draft.presetText = v; draft.presetTouched = true; }, "width:100%;box-sizing:border-box;min-height:180px;resize:vertical;"); - taP.placeholder = "该模式的设定词(改过的按「模式 + 语言」记进节点,换模式 / 换语言都不丢)"; + taP.placeholder = "该模式的设定词;改过的按「模式 + 语言」记进节点,换模式 / 换语言都不丢"; taP.spellcheck = false; body.append(taP); const taRow = el("div", "display:flex;align-items:center;gap:10px;margin:6px 0 4px;"); @@ -445,19 +476,20 @@ function xbrRenderPreset(body, node, draft, ctx) { } body.append(makeSectionTitle("增强预设(= 提示词设定 / system prompt)")); - const presetOpts = xbrWidgetOptions(node, "preset"); + const presetOpts = xbrFilterByLang(xbrWidgetOptions(node, "preset"), draft.lang); if (presetOpts.length) body.append(field("增强预设", selectControl(presetOpts, draft.preset, (v) => { draft.preset = v; }))); - body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 反推的系统提示词。")); + body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", + `该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 反推的系统提示词;只列与输出语言 ${draft.lang} 相符的预设。`)); body.append(makeSectionTitle("反推预设")); - const taskOpts = xbrWidgetOptions(node, "task_preset"); + const taskOpts = xbrFilterByLang(xbrWidgetOptions(node, "task_preset"), draft.lang); if (taskOpts.length) body.append(field("反推预设", selectControl(taskOpts, draft.task_preset, (v) => { draft.task_preset = v; }))); body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "带 * 的预设里 * 是必填占位符,由节点上的提示词框或外接「📝 提示词」填入。")); body.append(makeSectionTitle("追加设定(可留空)")); const ta = textareaControl(s.extra_system, (v) => { s.extra_system = v; }, "width:100%;box-sizing:border-box;min-height:200px;resize:vertical;"); - ta.placeholder = "追加在增强预设之后的额外要求(例:只输出一行、不要解释过程…)"; + ta.placeholder = "追加在增强预设之后的额外要求,例:只输出一行、不要解释过程…"; ta.spellcheck = false; body.append(ta); } @@ -472,22 +504,26 @@ function xbrRenderHelp(body) { " · 🖼️ 图像:外接图像 / 视频帧(LLM 反推:图 → 提示词)", "", "【输出端口】", - " · 📝 提示词:最终提示词(未启用 LLM = 拼装好的提示词;启用 LLM = 设定词原样置顶 + LLM 增强后的正文)", + " · 📝 提示词:最终提示词。未启用 LLM = 拼装好的提示词;启用 LLM = 设定词原封不动置顶 + LLM 增强后的正文", + " · 🖼️ 空latent:第二位。按「空latent类型」生成,官方 latent_format 逐字段对齐", " · 📋 提示词列表:按行拆分", - " · 🧩 提示词设定:本次的提示词设定(增强预设 + 输出语言 + 追加设定)", - " · 🖼️ 空latent:按「空latent类型」生成(官方 latent_format 逐字段对齐)", + " · 🧩 提示词设定:本次的提示词设定,即增强预设 + 输出语言 + 追加设定", "", "【节点表面】", " · 第 1 行按钮:🤖 LLM设置(唯一语言设置 / 后端 / 采样参数)| ✨ 增强预设(空latent类型 / 预设模式 / 增强预设 / 反推预设 / 追加设定)| 📖 使用说明", " · 第 2 行按钮:8 个元素分类(点选项行加入/移除提示词,【添加详细描述】写补充说明)", " · ✅ 启用 LLM 反推:关(默认)= 原「生图提示词预设」行为;开 = 图/文 → 提示词", - " · 设定词:预设模式(三视图 / 四视图 / 背景纯透明)的设定文本,在「✨ 增强预设」弹窗里编辑;" + " · 设定词:预设模式(三视图 / 四视图 / 五视图 / 背景纯透明)的设定文本,在「✨ 增强预设」弹窗里编辑;" + "改过的随节点保存,换模式 / 换语言不丢;输出时原封不动加在增强结果的顶端,也作为 LLM 的增强参考", " · 提示词框右上角「📋 复制」;下方「参数设定显示」实时显示当前配置", "", "【保存】", " · 弹窗底部:☑ 自动保存(默认开)+ 字号 + 界面缩放;「取消」丢弃未保存改动", " · 配置随工作流保存;在线 API 的 Key 存在 ComfyUI user 目录,不进工作流", + "", + "【语言 / 思考过程】", + " · 输出语言(🤖 LLM设置)是唯一语言设置:决定词表与设定词按哪种语言加载、增强预设与反推预设只列哪种语言的选项、最终提示词的语言", + " · 🧠 过滤思考过程(🤖 LLM设置 → 指令推理,默认开):系统提示词末尾追加「只输出最终提示词」硬规则;模型仍漏出思考过程时,自动剔掉思考标签、Final Output / 最终输出 之前的推理段与代码围栏", ]; body.append(el("div", "font-size:12px;color:#bbb;line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); } @@ -507,10 +543,13 @@ async function xbrOpenModal(node, panelId) { lang: xbrPick(String(xbrWidgetVal(node, "output_lang") ?? ""), LANGS, LANGS[0]), kind: xbrPick(String(xbrWidgetVal(node, "latent_kind") ?? ""), LATENT_KINDS, LATENT_KINDS[0]), mode: xbrPick(String(xbrWidgetVal(node, "preset_mode") ?? ""), MODES, MODES[0]), - // 设定词(三视图/四视图/背景纯透明):弹窗内草稿 + 「用户是否改过」标记 + // 设定词(三视图/四视图/五视图/背景纯透明):弹窗内草稿 + 「用户是否改过」标记 presetText: String(xbrWidgetVal(node, "three_view_text") ?? ""), presetTouched: false, }; + // 预设选项与输出语言对齐(旧工作流可能存着异语言的预设名) + draft.preset = xbrSnapPreset(xbrWidgetOptions(node, "preset"), draft.preset, draft.lang); + draft.task_preset = xbrSnapPreset(xbrWidgetOptions(node, "task_preset"), draft.task_preset, draft.lang); let apiSaved = null, providers = [XBR_DEFAULT_PROVIDER]; if (panelId === "llm") { diff --git a/js/xb_image_prompt_preset.js b/js/xb_image_prompt_preset.js index 3537da7..9364f83 100644 --- a/js/xb_image_prompt_preset.js +++ b/js/xb_image_prompt_preset.js @@ -2,7 +2,7 @@ * XB-BOX - 🖼️ 生图提示词预设 — 前端面板 * ============================================================ * 节点:XB_ImagePromptPreset(后端 nodes_image_prompt_preset.py,V1 经典 API) - * 表面:空latent类型 / 输出语言 / 预设模式 / (三视图预设句)/ 画幅比例 / 宽度 / 高度 / 生成数量 + * 表面:空latent类型 / 输出语言 / 预设模式 / (三视图/四视图/五视图预设句)/ 画幅比例 / 宽度 / 高度 / 生成数量 * + 节点提示词框 + 8 个分类按钮(4 列 × 2 行):🏷️ 风格 | 🎥 视角 | 👤 主体 | 🎬 姿态 | 👚 装扮 | 🎒 道具 | 💡 光影 | 🏞️ 背景 * 每个按钮一个面板,面板顶部「分类签」切子类;选项行整行可点(加入 → 变蓝 ✔ / 再点移除) * 道具类点行会先弹「与主体的关系」多宫格标签窗(30 个关系词) @@ -32,8 +32,9 @@ const LANGS = [LANG_ZH, LANG_EN]; const MODE_TP = "常规文生图"; const MODE_3V = "人物三视图"; const MODE_4V = "人物四视图"; +const MODE_5V = "人物五视图"; const MODE_RGBA = "背景纯透明"; -const MODES = [MODE_TP, MODE_3V, MODE_4V, MODE_RGBA]; +const MODES = [MODE_TP, MODE_3V, MODE_4V, MODE_5V, MODE_RGBA]; const ASPECT_MAP = { "1:1": 1, "16:9": 16 / 9, "9:16": 9 / 16, "4:3": 4 / 3, "3:4": 3 / 4, "21:9": 21 / 9 }; const SIZE_STEP = 16; // width/height 的兜底步长(实际以「空latent类型」的官方最小步长为准) @@ -71,6 +72,10 @@ const PRESET_TEXT_DEFAULT = { [LANG_ZH]: "生成四宫格排列的角色概念设计图,画面左上角面板是角色面部的精细特写肖像,画面右上角面板是角色面部侧面的的精细特写肖像,画面左下角面板是无头部人物正面衣着展示图,画面右下角面板是人物背面全身站姿。", [LANG_EN]: "Generate a character concept design sheet arranged in a 2x2 grid, the top-left panel is a finely detailed close-up portrait of the character's face, the top-right panel is a finely detailed close-up profile portrait of the character's face, the bottom-left panel is a headless front-facing outfit display view, the bottom-right panel is a full-body back standing pose.", }, + [MODE_5V]: { + [LANG_ZH]: "生成五宫格排列的角色概念设计图,画面左上角面板是角色面部的精细特写肖像,画面右上角面板是角色面部侧面的的精细特写肖像,画面下方左侧面板是无头部人物正面衣着展示图,画面下方中间面板是无头部人物侧面衣着展示图,画面下方右侧面板是人物背面全身站姿。", + [LANG_EN]: "Generate a character concept design sheet arranged in five panels, the top-left panel is a finely detailed close-up portrait of the character's face, the top-right panel is a finely detailed close-up profile portrait of the character's face, the bottom-left panel is a headless front-facing outfit display view, the bottom-middle panel is a headless side-facing outfit display view, the bottom-right panel is a full-body back standing pose.", + }, [MODE_RGBA]: { [LANG_ZH]: "生成一张具有透明度的 RGBA 格式图像,包含 Alpha 通道,背景为纯透明。", [LANG_EN]: "Generate an RGBA image with an alpha channel and a fully transparent background.", @@ -83,7 +88,7 @@ const THREE_VIEW_DEFAULT = PRESET_TEXT_DEFAULT[MODE_3V]; /** * ── 预设句(设定词)档案 ───────────────────────────────────────────── - * 需求:预设模式(三视图 / 四视图 / 背景纯透明)的设定词可编辑,且**改过就存进节点、 + * 需求:预设模式(三视图 / 四视图 / 五视图 / 背景纯透明)的设定词可编辑,且**改过就存进节点、 * 换模式 / 换语言都不丢**(切换时取「存档 → 默认」,绝不把用户改过的冲掉)。 * 存档位置 = manager_settings.preset_texts(JSON 字符串随工作流一起保存): * { "人物三视图|中文 [ZH]": "用户改过的设定词", … } ← 只存与默认不同的那条 @@ -2677,7 +2682,7 @@ function setupNode(node) { try { node.setDirtyCanvas?.(true, true); } catch (_) {} }; - // 预设句输入框:常规文生图以外的 3 个模式(三视图/四视图/背景纯透明)都显示(一次性隐藏/显示,不做任何 setSize,不会抖动) + // 预设句输入框:常规文生图以外的 4 个模式(三视图/四视图/五视图/背景纯透明)都显示(一次性隐藏/显示,不做任何 setSize,不会抖动) const applyPresetTextVisibility = () => { try { if (PRESET_TEXT_DEFAULT[surfaceOf().mode]) { showWidget(w3v); stylePresetTextBox(); } else hideWidget(w3v); diff --git a/js/xb_image_prompt_preset_pro.js b/js/xb_image_prompt_preset_pro.js index b05e874..a4e0651 100644 --- a/js/xb_image_prompt_preset_pro.js +++ b/js/xb_image_prompt_preset_pro.js @@ -2,7 +2,7 @@ * XB-BOX - 🖼️ 生图提示词预设 — 前端面板 * ============================================================ * 节点:XB_ImagePromptPreset(后端 nodes_image_prompt_preset.py,V1 经典 API) - * 表面:空latent类型 / 输出语言 / 预设模式 / (三视图预设句)/ 画幅比例 / 宽度 / 高度 / 生成数量 + * 表面:空latent类型 / 输出语言 / 预设模式 / (三视图/四视图/五视图预设句)/ 画幅比例 / 宽度 / 高度 / 生成数量 * + 节点提示词框 + 8 个分类按钮(4 列 × 2 行):🏷️ 风格 | 🎥 视角 | 👤 主体 | 🎬 姿态 | 👚 装扮 | 🎒 道具 | 💡 光影 | 🏞️ 背景 * 每个按钮一个面板,面板顶部「分类签」切子类;选项行整行可点(加入 → 变蓝 ✔ / 再点移除) * 道具类点行会先弹「与主体的关系」多宫格标签窗(30 个关系词) @@ -33,8 +33,9 @@ const LANGS = [LANG_ZH, LANG_EN]; const MODE_TP = "常规文生图"; const MODE_3V = "人物三视图"; const MODE_4V = "人物四视图"; +const MODE_5V = "人物五视图"; const MODE_RGBA = "背景纯透明"; -const MODES = [MODE_TP, MODE_3V, MODE_4V, MODE_RGBA]; +const MODES = [MODE_TP, MODE_3V, MODE_4V, MODE_5V, MODE_RGBA]; const ASPECT_MAP = { "1:1": 1, "16:9": 16 / 9, "9:16": 9 / 16, "4:3": 4 / 3, "3:4": 3 / 4, "21:9": 21 / 9 }; const SIZE_STEP = 16; // width/height 的兜底步长(实际以「空latent类型」的官方最小步长为准) @@ -72,6 +73,10 @@ const PRESET_TEXT_DEFAULT = { [LANG_ZH]: "生成四宫格排列的角色概念设计图,画面左上角面板是角色面部的精细特写肖像,画面右上角面板是角色面部侧面的的精细特写肖像,画面左下角面板是无头部人物正面衣着展示图,画面右下角面板是人物背面全身站姿。", [LANG_EN]: "Generate a character concept design sheet arranged in a 2x2 grid, the top-left panel is a finely detailed close-up portrait of the character's face, the top-right panel is a finely detailed close-up profile portrait of the character's face, the bottom-left panel is a headless front-facing outfit display view, the bottom-right panel is a full-body back standing pose.", }, + [MODE_5V]: { + [LANG_ZH]: "生成五宫格排列的角色概念设计图,画面左上角面板是角色面部的精细特写肖像,画面右上角面板是角色面部侧面的的精细特写肖像,画面下方左侧面板是无头部人物正面衣着展示图,画面下方中间面板是无头部人物侧面衣着展示图,画面下方右侧面板是人物背面全身站姿。", + [LANG_EN]: "Generate a character concept design sheet arranged in five panels, the top-left panel is a finely detailed close-up portrait of the character's face, the top-right panel is a finely detailed close-up profile portrait of the character's face, the bottom-left panel is a headless front-facing outfit display view, the bottom-middle panel is a headless side-facing outfit display view, the bottom-right panel is a full-body back standing pose.", + }, [MODE_RGBA]: { [LANG_ZH]: "生成一张具有透明度的 RGBA 格式图像,包含 Alpha 通道,背景为纯透明。", [LANG_EN]: "Generate an RGBA image with an alpha channel and a fully transparent background.", @@ -84,7 +89,7 @@ const THREE_VIEW_DEFAULT = PRESET_TEXT_DEFAULT[MODE_3V]; /** * ── 预设句(设定词)档案 ───────────────────────────────────────────── - * 需求:预设模式(三视图 / 四视图 / 背景纯透明)的设定词可编辑,且**改过就存进节点、 + * 需求:预设模式(三视图 / 四视图 / 五视图 / 背景纯透明)的设定词可编辑,且**改过就存进节点、 * 换模式 / 换语言都不丢**(切换时取「存档 → 默认」,绝不把用户改过的冲掉)。 * 存档位置 = manager_settings.preset_texts(JSON 字符串随工作流一起保存): * { "人物三视图|中文 [ZH]": "用户改过的设定词", … } ← 只存与默认不同的那条 @@ -2693,7 +2698,7 @@ function setupNode(node) { try { node.setDirtyCanvas?.(true, true); } catch (_) {} }; - // 预设句输入框:常规文生图以外的 3 个模式(三视图/四视图/背景纯透明)都显示(一次性隐藏/显示,不做任何 setSize,不会抖动) + // 预设句输入框:常规文生图以外的 4 个模式(三视图/四视图/五视图/背景纯透明)都显示(一次性隐藏/显示,不做任何 setSize,不会抖动) const applyPresetTextVisibility = () => { try { // Pro:设定词只在「✨ 增强预设」弹窗里编辑 → 节点表面始终隐藏(不把选项参数暴露在外面) @@ -3057,7 +3062,7 @@ const XBR_INFERENCE_MODES_ = XBR_INFERENCE_MODES; function xbrLlmDefaults() { return { model: { model: "", mmproj: "None", chat_handler: "None", n_ctx: 8192, vram_limit: -1, image_min_tokens: 0, image_max_tokens: 0 }, - run: { inference_mode: "one by one", max_frames: 24, max_size: 256, seed: 0, seed_control: "randomize", force_offload: false, save_states: false }, + run: { inference_mode: "one by one", max_frames: 24, max_size: 256, seed: 0, seed_control: "randomize", force_offload: false, save_states: false, strip_thinking: true }, params: { max_tokens: 6144, top_k: 40, top_p: 0.9, min_p: 0.05, typical_p: 1.0, temperature: 0.6, repeat_penalty: 1.12, frequency_penalty: 0.0, present_penalty: 0.0, @@ -3087,6 +3092,7 @@ function xbrParseLlm(raw) { cfg.run.seed_control = xbrPick(r.seed_control, XBR_SEED_MODES, cfg.run.seed_control); cfg.run.force_offload = xbrFlag(r.force_offload, cfg.run.force_offload); cfg.run.save_states = xbrFlag(r.save_states, cfg.run.save_states); + cfg.run.strip_thinking = xbrFlag(r.strip_thinking, cfg.run.strip_thinking); // 模型输出思考过程时自动过滤 const p = (d.params && typeof d.params === "object") ? d.params : {}; cfg.params.max_tokens = xbrInt(p.max_tokens, cfg.params.max_tokens, 0, 262144); @@ -3135,7 +3141,7 @@ function xbrSaveJson(node, patch) { return cur; } -/* ── 预设设定词(三视图 / 四视图 / 背景纯透明的设定文本)───────────── +/* ── 预设设定词(三视图 / 四视图 / 五视图 / 背景纯透明的设定文本)───────────── * · 用户改过的按「模式|语言」存进节点(基础面板的 preset_texts 存档); * · 弹窗读「存档 → 默认」,提交时同步写 widget + 存档。 */ function xbrPresetStore(node) { @@ -3146,6 +3152,28 @@ function xbrPresetTextFor(node, mode, lang) { return (t && String(t).trim()) ? t : (defaultPresetOf(mode, lang) || ""); } +/* ── 预设选项按「输出语言」过滤 ─────────────────────────────── + * 预设名的语言标签形如 "Z-Image Turbo [ZH]" / "Normal - 描述 [EN]"; + * 输出语言选中文 → 增强预设 / 反推预设 只列 [ZH] 项,选英文 → 只列 [EN] 项。 */ +function xbrLangTagOf(v) { + const m = String(v == null ? "" : v).match(/[\[[](ZH|EN)[\]]]\s*$/i); + return m ? m[1].toUpperCase() : ""; +} +function xbrFilterByLang(opts, lang) { + const want = xbrLangTagOf(lang); + if (!want) return opts.slice(); + const hit = opts.filter((o) => xbrLangTagOf(o) === want); + return hit.length ? hit : opts.slice(); // 全无语言标签(老预设)→ 全量兜底 +} +/** 语言变了:优先换到「同名的另一语言项」,否则取该语言第一项 */ +function xbrSnapPreset(opts, cur, lang) { + const list = xbrFilterByLang(opts, lang); + if (list.includes(cur)) return cur; + const base = String(cur == null ? "" : cur).replace(/\s*[\[[](ZH|EN)[\]]]\s*$/i, "").trim(); + const same = list.find((o) => String(o).replace(/\s*[\[[](ZH|EN)[\]]]\s*$/i, "").trim() === base); + return same || list[0] || cur; +} + /* ── 在线 API(存 ComfyUI user 目录,Key 不进工作流)──────── */ async function xbrApiGet() { const resp = await api.fetchApi(XBR_API_ENDPOINT); @@ -3342,8 +3370,11 @@ function xbrRenderLlm(body, node, draft, ctx) { draft.lang = v; // 换语言 → 未在弹窗里改过设定词时,跟着取该语言的「存档 → 默认」 if (!draft.presetTouched) draft.presetText = xbrPresetTextFor(node, draft.mode, v); + // 语言决定预设可选范围:增强预设 / 反推预设 自动换到同语言的同名项(没有则取该语言第一项) + draft.preset = xbrSnapPreset(xbrWidgetOptions(node, "preset"), draft.preset, v); + draft.task_preset = xbrSnapPreset(xbrWidgetOptions(node, "task_preset"), draft.task_preset, v); }))); - body.append(xbrHint("本项是唯一的语言设置:① 元素词表 / 预设句按哪种语言加载 ② LLM 反推最终输出的提示词是中文还是英文。其他地方不再有语言选项。")); + body.append(xbrHint("本项是唯一的语言设置:① 词表 / 设定词 / 预设句按哪种语言加载 ② 增强预设与反推预设只列该语言的选项 ③ 最终输出的提示词语言。")); body.append(makeSectionTitle("提示词增强反推(总开关在节点表面的「启用 LLM 反推」)")); @@ -3356,7 +3387,7 @@ function xbrRenderLlm(body, node, draft, ctx) { const llm = s.model; const models = lists.model.length ? lists.model : (llm.model ? [llm.model] : []); if (!llm.model && models.length) llm.model = models[0]; - body.append(field("强制卸载", checkboxControl(s.run.force_offload, "LLM 用完即卸载(释放显存)", (v) => { s.run.force_offload = v; }))); + body.append(field("强制卸载", checkboxControl(s.run.force_offload, "LLM 用完即卸载,释放显存", (v) => { s.run.force_offload = v; }))); if (models.length) body.append(field("模型", selectControl(models, llm.model, (v) => { llm.model = v; }))); else body.append(field("模型", xbrTextControl(llm.model, "未找到本地 LLM 模型(.gguf 放到 models/LLM)", (v) => { llm.model = v; }))); body.append(field("视觉模块 mmproj", selectControl(lists.mmproj.length ? lists.mmproj : ["None"], llm.mmproj, (v) => { llm.mmproj = v; }))); @@ -3386,7 +3417,7 @@ function xbrRenderLlm(body, node, draft, ctx) { const a = draft.api || (draft.api = { ...XBR_API_DEFAULTS }); body.append(field("服务商", selectControl(ctx.providers, a.provider, (v) => { a.provider = v; }))); body.append(field("模型", xbrTextControl(a.model, XBR_API_DEFAULTS.model, (v) => { a.model = v; }))); - body.append(field("API Key", xbrPasswordControl(a.api_key, "sk-…(保存在本地,不写入工作流)", (v) => { a.api_key = v; }))); + body.append(field("API Key", xbrPasswordControl(a.api_key, "sk-… 保存在本地,不写入工作流", (v) => { a.api_key = v; }))); body.append(field("Base URL", xbrTextControl(a.base_url, XBR_API_DEFAULTS.base_url, (v) => { a.base_url = v; }))); body.append(field("temperature", xbrNumberControl(a.temperature, { min: 0, max: 2, step: 0.01 }, (v) => { a.temperature = v; }))); body.append(field("max_tokens", xbrNumberControl(a.max_tokens, { min: 1, max: 262144, step: 1 }, (v) => { a.max_tokens = Math.round(v); }))); @@ -3402,8 +3433,13 @@ function xbrRenderLlm(body, node, draft, ctx) { body.append(field("推理模式", selectControl(XBR_INFERENCE_MODES, s.run.inference_mode, (v) => { s.run.inference_mode = v; }))); body.append(field("最大帧数", xbrNumberControl(s.run.max_frames, { min: 2, max: 1024, step: 1 }, (v) => { s.run.max_frames = Math.round(v); }))); body.append(field("最大尺寸", xbrNumberControl(s.run.max_size, { min: 128, max: 16384, step: 64 }, (v) => { s.run.max_size = Math.round(v); }))); - body.append(field("保存对话状态", checkboxControl(s.run.save_states, "在内存中保留本次对话上下文(多轮连续反推)", (v) => { s.run.save_states = v; }))); + body.append(field("保存对话状态", checkboxControl(s.run.save_states, "在内存中保留本次对话上下文,多轮连续反推", (v) => { s.run.save_states = v; }))); body.append(field("状态 UID", xbrNumberControl(s.params.state_uid, { min: -1, max: 999999, step: 1 }, (v) => { s.params.state_uid = Math.round(v); }))); + body.append(field("🧠 过滤思考过程", checkboxControl(s.run.strip_thinking, + "模型把「思考过程 / 推理段 / 工作流程」一起输出时,自动只保留最终提示词", (v) => { s.run.strip_thinking = v; }))); + body.append(el("div", "font-size:11px;color:#888;line-height:1.65;margin:2px 0 8px;", + "开启后=① 系统提示词末尾追加「只输出最终提示词」硬规则 ② 仍漏出思考时自动剔除思考标签、" + + "Final Output / 最终输出 标记之前的推理段与代码围栏(拿不到内容则原文保留)。")); body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "提示词正文在节点上的提示词框里编辑;「📝 提示词」端口接线后该框锁定。")); } @@ -3421,7 +3457,7 @@ function xbrRenderPreset(body, node, draft, ctx) { draft.presetText = xbrPresetTextFor(node, v, draft.lang); ctx.rerender(); // 有/无设定词的模式之间切换 → 重画「设定词」区 }))); - body.append(xbrHint("常规文生图 = 只输出正文;人物三视图 / 人物四视图 / 背景纯透明 = 最终提示词最顶端自动加上该模式的设定词。")); + body.append(xbrHint("常规文生图 = 只输出正文;人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 = 最终提示词最顶端自动加上该模式的设定词。")); // ── 设定词(预设模式对应的设定文本;只在弹窗里编辑,节点表面不显示)── const modeDef = defaultPresetOf(draft.mode, draft.lang); @@ -3430,7 +3466,7 @@ function xbrRenderPreset(body, node, draft, ctx) { if (!String(draft.presetText || "").trim()) draft.presetText = modeDef; const taP = textareaControl(draft.presetText, (v) => { draft.presetText = v; draft.presetTouched = true; }, "width:100%;box-sizing:border-box;min-height:180px;resize:vertical;"); - taP.placeholder = "该模式的设定词(改过的按「模式 + 语言」记进节点,换模式 / 换语言都不丢)"; + taP.placeholder = "该模式的设定词;改过的按「模式 + 语言」记进节点,换模式 / 换语言都不丢"; taP.spellcheck = false; body.append(taP); const taRow = el("div", "display:flex;align-items:center;gap:10px;margin:6px 0 4px;"); @@ -3449,19 +3485,20 @@ function xbrRenderPreset(body, node, draft, ctx) { } body.append(makeSectionTitle("增强预设(= 提示词设定 / system prompt)")); - const presetOpts = xbrWidgetOptions(node, "preset"); + const presetOpts = xbrFilterByLang(xbrWidgetOptions(node, "preset"), draft.lang); if (presetOpts.length) body.append(field("增强预设", selectControl(presetOpts, draft.preset, (v) => { draft.preset = v; }))); - body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 反推的系统提示词。")); + body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", + `该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 反推的系统提示词;只列与输出语言 ${draft.lang} 相符的预设。`)); body.append(makeSectionTitle("反推预设")); - const taskOpts = xbrWidgetOptions(node, "task_preset"); + const taskOpts = xbrFilterByLang(xbrWidgetOptions(node, "task_preset"), draft.lang); if (taskOpts.length) body.append(field("反推预设", selectControl(taskOpts, draft.task_preset, (v) => { draft.task_preset = v; }))); body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "带 * 的预设里 * 是必填占位符,由节点上的提示词框或外接「📝 提示词」填入。")); body.append(makeSectionTitle("追加设定(可留空)")); const ta = textareaControl(s.extra_system, (v) => { s.extra_system = v; }, "width:100%;box-sizing:border-box;min-height:200px;resize:vertical;"); - ta.placeholder = "追加在增强预设之后的额外要求(例:只输出一行、不要解释过程…)"; + ta.placeholder = "追加在增强预设之后的额外要求,例:只输出一行、不要解释过程…"; ta.spellcheck = false; body.append(ta); } @@ -3476,22 +3513,26 @@ function xbrRenderHelp(body) { " · 🖼️ 图像:外接图像 / 视频帧(LLM 反推:图 → 提示词)", "", "【输出端口】", - " · 📝 提示词:最终提示词(未启用 LLM = 拼装好的提示词;启用 LLM = 设定词原样置顶 + LLM 增强后的正文)", + " · 📝 提示词:最终提示词。未启用 LLM = 拼装好的提示词;启用 LLM = 设定词原封不动置顶 + LLM 增强后的正文", + " · 🖼️ 空latent:第二位。按「空latent类型」生成,官方 latent_format 逐字段对齐", " · 📋 提示词列表:按行拆分", - " · 🧩 提示词设定:本次的提示词设定(增强预设 + 输出语言 + 追加设定)", - " · 🖼️ 空latent:按「空latent类型」生成(官方 latent_format 逐字段对齐)", + " · 🧩 提示词设定:本次的提示词设定,即增强预设 + 输出语言 + 追加设定", "", "【节点表面】", " · 第 1 行按钮:🤖 LLM设置(唯一语言设置 / 后端 / 采样参数)| ✨ 增强预设(空latent类型 / 预设模式 / 增强预设 / 反推预设 / 追加设定)| 📖 使用说明", " · 第 2 行按钮:8 个元素分类(点选项行加入/移除提示词,【添加详细描述】写补充说明)", " · ✅ 启用 LLM 反推:关(默认)= 原「生图提示词预设」行为;开 = 图/文 → 提示词", - " · 设定词:预设模式(三视图 / 四视图 / 背景纯透明)的设定文本,在「✨ 增强预设」弹窗里编辑;" + " · 设定词:预设模式(三视图 / 四视图 / 五视图 / 背景纯透明)的设定文本,在「✨ 增强预设」弹窗里编辑;" + "改过的随节点保存,换模式 / 换语言不丢;输出时原封不动加在增强结果的顶端,也作为 LLM 的增强参考", " · 提示词框右上角「📋 复制」;下方「参数设定显示」实时显示当前配置", "", "【保存】", " · 弹窗底部:☑ 自动保存(默认开)+ 字号 + 界面缩放;「取消」丢弃未保存改动", " · 配置随工作流保存;在线 API 的 Key 存在 ComfyUI user 目录,不进工作流", + "", + "【语言 / 思考过程】", + " · 输出语言(🤖 LLM设置)是唯一语言设置:决定词表与设定词按哪种语言加载、增强预设与反推预设只列哪种语言的选项、最终提示词的语言", + " · 🧠 过滤思考过程(🤖 LLM设置 → 指令推理,默认开):系统提示词末尾追加「只输出最终提示词」硬规则;模型仍漏出思考过程时,自动剔掉思考标签、Final Output / 最终输出 之前的推理段与代码围栏", ]; body.append(el("div", "font-size:12px;color:#bbb;line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); } @@ -3511,10 +3552,13 @@ async function xbrOpenModal(node, panelId) { lang: xbrPick(String(xbrWidgetVal(node, "output_lang") ?? ""), LANGS, LANGS[0]), kind: xbrPick(String(xbrWidgetVal(node, "latent_kind") ?? ""), LATENT_KINDS, LATENT_KINDS[0]), mode: xbrPick(String(xbrWidgetVal(node, "preset_mode") ?? ""), MODES, MODES[0]), - // 设定词(三视图/四视图/背景纯透明):弹窗内草稿 + 「用户是否改过」标记 + // 设定词(三视图/四视图/五视图/背景纯透明):弹窗内草稿 + 「用户是否改过」标记 presetText: String(xbrWidgetVal(node, "three_view_text") ?? ""), presetTouched: false, }; + // 预设选项与输出语言对齐(旧工作流可能存着异语言的预设名) + draft.preset = xbrSnapPreset(xbrWidgetOptions(node, "preset"), draft.preset, draft.lang); + draft.task_preset = xbrSnapPreset(xbrWidgetOptions(node, "task_preset"), draft.task_preset, draft.lang); let apiSaved = null, providers = [XBR_DEFAULT_PROVIDER]; if (panelId === "llm") { diff --git a/locales/en/nodeDefs.json b/locales/en/nodeDefs.json index 758c66d..cb5ade0 100644 --- a/locales/en/nodeDefs.json +++ b/locales/en/nodeDefs.json @@ -357,7 +357,30 @@ } }, "XB_ImagePromptPresetPro": { - "display_name": "XB-BOX - 🖼️ Image Prompt Preset Pro" + "display_name": "XB-BOX - 🖼️ Image Prompt Preset Pro", + "description": "Image Prompt Preset + Prompt Enhance/Reverse fused: empty latent type + aspect params + 8-category element panel; optional LLM enhance/reverse (local model or online API, enhance preset / task preset / extra settings / sampling params). Outputs: prompt / prompt list / prompt settings / empty latent.", + "inputs": { + "latent_kind": { "name": "Empty Latent Type" }, + "output_lang": { "name": "Output Language" }, + "preset_mode": { "name": "Preset Mode" }, + "three_view_text": { "name": "Preset Text (3/4/5-view / transparent)" }, + "aspect_ratio": { "name": "Aspect Ratio" }, + "width": { "name": "Width" }, + "height": { "name": "Height" }, + "batch_size": { "name": "Batch Size" }, + "use_llm": { "name": "✅ Enable LLM Enhance" }, + "backend": { "name": "LLM Backend" }, + "preset": { "name": "Enhance Preset" }, + "task_preset": { "name": "Reverse Preset" }, + "text": { "name": "📝 Prompt" }, + "images": { "name": "🖼️ Image" } + }, + "outputs": { + "0": { "name": "📝 Prompt" }, + "1": { "name": "�️ Empty Latent" }, + "2": { "name": "📋 Prompt List" }, + "3": { "name": "🧩 Prompt Settings" } + } }, "XB_ImagePromptPreset": { "display_name": "XB-BOX - 🖼️ Image Prompt Preset", @@ -365,7 +388,7 @@ "output_lang": { "name": "Output Language" }, "preset_mode": { "name": "Preset Mode" }, "latent_kind": { "name": "Empty Latent Type" }, - "three_view_text": { "name": "Preset Text (3-view/4-view/transparent)" }, + "three_view_text": { "name": "Preset Text (3/4/5-view / transparent)" }, "aspect_ratio": { "name": "Aspect Ratio" }, "width": { "name": "Width" }, "height": { "name": "Height" }, diff --git a/locales/zh/nodeDefs.json b/locales/zh/nodeDefs.json index 3d783cf..8a180f6 100644 --- a/locales/zh/nodeDefs.json +++ b/locales/zh/nodeDefs.json @@ -5888,12 +5888,12 @@ }, "XB_ImagePromptPresetPro": { "display_name": "XB-BOX - 🖼️ 生图提示词预设Pro", - "description": "生图提示词预设 + 提示词增强反推 融合:空latent类型/画幅参数 + 8 大类元素面板拼装提示词;可选 LLM 反推(本地模型或在线 API,增强预设 / 反推预设 / 追加设定 / 采样参数 / 生成后控制)。输出:提示词 / 提示词列表 / 提示词设定 / 空latent。", + "description": "生图提示词预设 + 提示词增强反推 融合:空latent类型/画幅参数 + 8 大类元素面板拼装提示词;可选 LLM 反推(本地模型或在线 API,增强预设 / 反推预设 / 追加设定 / 采样参数 / 生成后控制)。输出:提示词 / 空latent / 提示词列表 / 提示词设定。", "inputs": { "latent_kind": { "name": "空latent类型" }, "output_lang": { "name": "输出语言" }, "preset_mode": { "name": "预设模式" }, - "three_view_text": { "name": "预设句(三视图/四视图/透明背景)" }, + "three_view_text": { "name": "预设句(三/四/五视图·透明背景)" }, "aspect_ratio": { "name": "画幅比例" }, "width": { "name": "宽度" }, "height": { "name": "高度" }, @@ -5907,9 +5907,9 @@ }, "outputs": { "0": { "name": "📝 提示词" }, - "1": { "name": "📋 提示词列表" }, - "2": { "name": "🧩 提示词设定" }, - "3": { "name": "🖼️ 空latent" } + "1": { "name": "�️ 空latent" }, + "2": { "name": "📋 提示词列表" }, + "3": { "name": "🧩 提示词设定" } } }, "XB_ImagePromptPreset": { @@ -5918,7 +5918,7 @@ "output_lang": { "name": "输出语言" }, "preset_mode": { "name": "预设模式" }, "latent_kind": { "name": "空latent类型" }, - "three_view_text": { "name": "预设句(三视图/四视图/透明背景)" }, + "three_view_text": { "name": "预设句(三/四/五视图·透明背景)" }, "aspect_ratio": { "name": "画幅比例" }, "width": { "name": "宽度" }, "height": { "name": "高度" }, diff --git a/nodes_image_prompt_preset.py b/nodes_image_prompt_preset.py index 9526bb3..cf44c86 100644 --- a/nodes_image_prompt_preset.py +++ b/nodes_image_prompt_preset.py @@ -3,7 +3,7 @@ 一个节点顶替「提示词输入框 + 画幅比例节点 + 官方空 latent」三件套: · 表面参数(顺序 = 节点上从上到下的显示顺序): - 空latent类型 / 输出语言 / 预设模式 / 预设句(三视图·四视图·背景纯透明 三个模式显示) + 空latent类型 / 输出语言 / 预设模式 / 预设句(三视图·四视图·五视图·背景纯透明 四个模式显示) / 画幅比例 / 宽度 / 高度 / 生成数量 —— 画幅比例 + 步长锁定 + 比例联动 100% 复刻本包「XB-BOX - 🖼️ 图片参数大全」 (去掉强度(小数)/强度(整数)/缩放尺寸三项) @@ -11,7 +11,7 @@ 🏷️ 风格 | 🎥 视角 | 👤 主体 | 🎬 姿态 | 👚 装扮 | 🎒 道具 | 💡 光影 | 🏞️ 背景 每个按钮打开**该分类**的独立面板;面板顶部「分类签」切换子类 (例:风格 → 真人/动画/3D/绘画插画/特殊风格),选项行 = ➕ ➖ 选项名 添加详细描述 -· 输出①「提示词」= 预设句(人物三视图 / 人物四视图 / 背景纯透明;常规文生图无预设句)+ 正文(节点提示词框 ↔ 面板预览框双向同步) +· 输出①「提示词」= 预设句(人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明;常规文生图无预设句)+ 正文(节点提示词框 ↔ 面板预览框双向同步) · 输出②「空latent」= 主流模型直接可用的空 latent(选项名 = 模型名,规格按官方 latent_format; 通道数 / 下采样除数 / 步长 / 上下限 / batch 上限 / downscale 元数据逐项对齐): · Z-image 16 通道 · /8 · 步长 16 (官方 latent_format = Flux) @@ -52,20 +52,23 @@ MAX_RESOLUTION = int(getattr(nodes, "MAX_RESOLUTION", 16384) or 16384) # ── 面板枚举(字符串即存储值,前端面板与后端按同一份字符串对齐) ──────────── OUTPUT_LANGS = ["中文 [ZH]", "英文 [EN]"] -PRESET_MODES = ["常规文生图", "人物三视图", "人物四视图", "背景纯透明"] +PRESET_MODES = ["常规文生图", "人物三视图", "人物四视图", "人物五视图", "背景纯透明"] LANG_ZH = OUTPUT_LANGS[0] MODE_TEXT2IMG = PRESET_MODES[0] MODE_THREE_VIEW = PRESET_MODES[1] MODE_FOUR_VIEW = PRESET_MODES[2] -MODE_TRANSPARENT = PRESET_MODES[3] +MODE_FIVE_VIEW = PRESET_MODES[3] +MODE_TRANSPARENT = PRESET_MODES[4] # ── 主流模型的空 latent 规格表(逐项抄自 ComfyUI:comfy/latent_formats.py + 官方空 latent 节点)── # 选项文字 = 模型名(干净,不带专业参数;细节放 tooltip 与日志);规格仍完整保留以保证对得上模型: -# 每行: (选项名, 通道数, 下采样除数_h, _w, 尺寸步长, 最小, 最大, batch上限, downscale_ratio_spacial)# · 尺寸步长 = 各模型**官方最小步长**(= 下采样 × patch2): +# 每行: (选项名, 通道数, 下采样除数_h, _w, 尺寸步长, 最小, 最大, batch上限, downscale_ratio_spacial) +# · 尺寸步长 = 各模型**官方最小步长**(= 下采样 × patch2): # Anima/Boogu/SDXL = 8(/8)、Flux2 = 16(/16)、Krea2/SD3/Z-image = 16(/8×patch2)、 # Qwen-image = 32(用户指定:Qwen 系工作流习惯锁 32)、Hunyuan = 32(/32)。 -# 不做全局统一的 32,避免把 1080 这类尺寸无谓地顶到 1088。# · downscale_ratio_spacial = 官方 EmptyLatentImage/EmptySD3 带回的元数据(供 +# 不做全局统一的 32,避免把 1080 这类尺寸无谓地顶到 1088。 +# · downscale_ratio_spacial = 官方 EmptyLatentImage/EmptySD3 带回的元数据(供 # comfy/sample.py:fix_empty_latent_channels 判断是否需要缩放尺寸) _OFFICIAL_LATENT_ROWS = [ ("Anima", 16, 8, 8, 8, 16, MAX_RESOLUTION, 4096, 8), @@ -92,7 +95,7 @@ LATENT_KIND_SPEC = { DEFAULT_LATENT_KIND = "Z-image" # 各预设模式的默认预设句(可编辑字段 three_view_text 的默认值):按「预设模式 × 输出语言」给。 # · 常规文生图 = 无预设句(原样输出正文,用户完全可控) -# · 人物三视图 / 人物四视图 / 背景纯透明 = 成句时自动把预设句拼在正文前(中文「。」/英文 ". ") +# · 人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 = 成句时自动把预设句拼在正文前(中文「。」/英文 ". ") PRESET_TEXT = { MODE_THREE_VIEW: { LANG_ZH: "生成平行排列的角色概念设计图,画面从左到右由四个独立面板组成:" @@ -115,11 +118,24 @@ PRESET_TEXT = { "the bottom-left panel is a headless front-facing outfit display view, " "the bottom-right panel is a full-body back standing pose.", }, + MODE_FIVE_VIEW: { + LANG_ZH: "生成五宫格排列的角色概念设计图,画面左上角面板是角色面部的精细特写肖像," + "画面右上角面板是角色面部侧面的的精细特写肖像," + "画面下方左侧面板是无头部人物正面衣着展示图,画面下方中间面板是无头部人物侧面衣着展示图," + "画面下方右侧面板是人物背面全身站姿。", + OUTPUT_LANGS[1]: "Generate a character concept design sheet arranged in five panels, " + "the top-left panel is a finely detailed close-up portrait of the character's face, " + "the top-right panel is a finely detailed close-up profile portrait of the character's face, " + "the bottom-left panel is a headless front-facing outfit display view, " + "the bottom-middle panel is a headless side-facing outfit display view, " + "the bottom-right panel is a full-body back standing pose.", + }, MODE_TRANSPARENT: { LANG_ZH: "生成一张具有透明度的 RGBA 格式图像,包含 Alpha 通道,背景为纯透明。", OUTPUT_LANGS[1]: "Generate an RGBA image with an alpha channel and a fully transparent background.", }, } + # 兼容旧引用(三视图预设句) THREE_VIEW_TEXT = PRESET_TEXT[MODE_THREE_VIEW] @@ -322,7 +338,7 @@ def build_empty_latent(latent_kind, width, height, batch_size=1): # 提示词拼装 # ============================================================================ def build_prompt(lang, preset_mode, three_view_text, body): - """预设句(人物三视图 / 人物四视图 / 背景纯透明) + 正文,按输出语言使用合适的分句符。 + """预设句(人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明) + 正文,按输出语言使用合适的分句符。 正文 = 节点表面提示词框(也等于面板预览框,两者双向同步)。 常规文生图:预设句不参与 → 原样输出正文(保持用户完全可控)。 @@ -347,7 +363,7 @@ def build_prompt(lang, preset_mode, three_view_text, body): class XB_ImagePromptPreset: """XB-BOX - 🖼️ 生图提示词预设:画幅参数 + 提示词拼装 + 官方空 latent 一体化。 - · 表面参数顺序 = 输出语言 / 预设模式 / 空latent类型 / 预设句(三视图·四视图·背景纯透明 显示) + · 表面参数顺序 = 输出语言 / 预设模式 / 空latent类型 / 预设句(三视图·四视图·五视图·背景纯透明 显示) / 画幅比例 / 宽度 / 高度 / 生成数量; · 「提示词」输出 = 预设句(人物三视图)+ 节点提示词框正文; · 「空latent」输出 = 官方空 latent(见 LATENT_KINDS,共 9 种,逐字段对齐,选项按首字母排列)。 @@ -374,12 +390,12 @@ class XB_ImagePromptPreset: "preset_mode": (list(PRESET_MODES), { "default": PRESET_MODES[0], "tooltip": "预设模式:常规文生图=只输出正文(完全可控);" - "人物三视图 / 人物四视图 / 背景纯透明 = 成句自动在正文前加对应预设句" + "人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 = 成句自动在正文前加对应预设句" "(下方预设句框仅这三个模式显示,切模式时未改过的默认句会自动跟随)", }), "three_view_text": ("STRING", { "default": THREE_VIEW_TEXT[LANG_ZH], "multiline": True, - "tooltip": "预设句 / 设定词(人物三视图 / 人物四视图 / 背景纯透明 三个模式生效;常规文生图时本框自动隐藏)\n" + "tooltip": "预设句 / 设定词(人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 四个模式生效;常规文生图时本框自动隐藏)\n" "· 用户改过的设定词按「模式 + 语言」存进本节点(换模式 / 换语言都不会丢);\n" "· 最终提示词输出时它会被原封不动加在正文最顶端", }), @@ -407,7 +423,7 @@ class XB_ImagePromptPreset: "internal_prompt": ("STRING", { "default": "", "multiline": True, "tooltip": "节点内编辑的提示词正文(与元素面板的预览框双向同步,随工作流保存)。" - "人物三视图 / 人物四视图 / 背景纯透明模式下,本字段前面会自动拼上对应预设句", + "人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明模式下,本字段前面会自动拼上对应预设句", }), "manager_settings": ("STRING", { "default": "", "multiline": True, @@ -465,7 +481,7 @@ class XB_ImagePromptPreset: __all__ = [ "XB_ImagePromptPreset", "ASPECT_RATIO_OPTIONS", "ASPECT_RATIO_MAP", "SIZE_STEP", "SIZE_MIN", "MAX_RESOLUTION", - "BATCH_MAX", "OUTPUT_LANGS", "PRESET_MODES", "PRESET_TEXT", "THREE_VIEW_TEXT", + "BATCH_MAX", "MODE_FIVE_VIEW", "OUTPUT_LANGS", "PRESET_MODES", "PRESET_TEXT", "THREE_VIEW_TEXT", "LATENT_KINDS", "LATENT_KIND_SPEC", "DEFAULT_LATENT_KIND", "ELEMENT_CATEGORIES", "default_settings", "parse_settings", "round_step", "normalize_size", "latent_kind_spec", "latent_step_of", "latent_shape_of", diff --git a/nodes_image_prompt_preset_pro.py b/nodes_image_prompt_preset_pro.py index 6451cd8..dfd51ae 100644 --- a/nodes_image_prompt_preset_pro.py +++ b/nodes_image_prompt_preset_pro.py @@ -5,7 +5,7 @@ XB-BOX - 🖼️ 生图提示词预设Pro ① 生图预设(原节点全部能力) · 空latent类型(9 种主流模型,逐字段对齐官方 latent_format) - · 输出语言 / 预设模式 / 设定词(三视图·四视图·背景纯透明) + · 输出语言 / 预设模式 / 设定词(三视图·四视图·五视图·背景纯透明) - 设定词既作为 LLM 的**增强参考**,也(启用 LLM 时)被**原封不动**加在最终提示词最顶端; - 用户改过的设定词按「模式 + 语言」存在本节点里,换模式 / 换语言都不会丢。 · 画幅比例 / 宽度 / 高度 / 生成数量(步长按模型官方最小步长 8/16/32 + 比例联动) @@ -22,7 +22,7 @@ XB-BOX - 🖼️ 生图提示词预设Pro · 关(默认)= 原「生图提示词预设」行为:只输出拼装好的提示词 + 空latent(不加载模型、不调 API) · 开 = 把(外接「提示词」端口 或 节点上拼装好的提示词)+ 外接「图像」交给 LLM 反推 -输出:提示词(最终)/ 提示词列表 / 提示词设定 / 空latent +输出:提示词(最终)/ 空latent / 提示词列表 / 提示词设定 (两个旧节点全部保留,本节点只是融合超集) CATEGORY: XB_ToolBox/Image_Params @@ -38,11 +38,13 @@ from .nodes_llama import ( preset_tags, ) from .nodes_llama_prompt_reverse import ( + OUTPUT_FORMAT_HINT, _LANG_HINT, _OUTPUT_LANGS, _SEED_MAX, build_api_config, parse_settings as parse_llm_settings, + strip_reasoning, ) from .nodes_image_prompt_preset import ( ASPECT_RATIO_OPTIONS, @@ -102,10 +104,11 @@ def _next_seed(seed, mode): def _build_system_prompt(preset, extra, output_lang, preset_text=""): - """提示词设定 = 增强预设 + 输出语言 + 设定词参考 + 追加设定 + """提示词设定 = 增强预设 + 输出语言 + 设定词参考 + 追加设定 + 只输出最终提示词的硬规则 `preset_text`(预设模式的设定词)只作为**增强参考**告诉 LLM:它会被节点原样置顶到最终提示词, 禁止在正文里重复 / 改写它。 + 最后追加 `OUTPUT_FORMAT_HINT`(最高优先级)——专治模型把思考过程 / 步骤 / 说明一起输出。 """ parts = [XB_llamaPromptEnhancer().main(preset)[0] or ""] parts.append(_LANG_HINT.get(output_lang, _LANG_HINT[_OUTPUT_LANGS[0]])) @@ -115,6 +118,7 @@ def _build_system_prompt(preset, extra, output_lang, preset_text=""): extra = (extra or "").strip() if extra: parts.append(extra) + parts.append(OUTPUT_FORMAT_HINT) # 放最后 = 最显眼(用户要求:不要思考过程) return "\n\n".join([p for p in parts if p]) @@ -154,11 +158,11 @@ class XB_ImagePromptPresetPro: }), "preset_mode": (list(PRESET_MODES), { "default": PRESET_MODES[0], - "tooltip": "预设模式:常规文生图=只输出正文;人物三视图 / 人物四视图 / 背景纯透明 = 自动在正文前加预设句", + "tooltip": "预设模式:常规文生图=只输出正文;人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 = 自动在正文前加预设句", }), "three_view_text": ("STRING", { "default": THREE_VIEW_TEXT[DEFAULT_LANG], "multiline": True, - "tooltip": "预设模式的设定词(人物三视图 / 人物四视图 / 背景纯透明 三个模式生效;常规文生图时自动隐藏)\n" + "tooltip": "预设模式的设定词(人物三视图 / 人物四视图 / 人物五视图 / 背景纯透明 四个模式生效;常规文生图时自动隐藏)\n" "· 最终提示词输出时它会被【原封不动】加在最顶端;\n" "· 用户改过的设定词按「模式 + 语言」存进本节点(换模式 / 换语言都不会丢);\n" "· 启用 LLM 时它作为增强参考交给 LLM(LLM 只增强正文,不改写它)", @@ -227,14 +231,14 @@ class XB_ImagePromptPresetPro: }, } - RETURN_TYPES = ("STRING", "STRING", "STRING", "LATENT") - RETURN_NAMES = ("提示词", "提示词列表", "提示词设定", "空latent") - OUTPUT_IS_LIST = (False, True, False, False) + RETURN_TYPES = ("STRING", "LATENT", "STRING", "STRING") + RETURN_NAMES = ("提示词", "空latent", "提示词列表", "提示词设定") + OUTPUT_IS_LIST = (False, False, True, False) FUNCTION = "generate" CATEGORY = "XB_ToolBox/Image_Params" DESCRIPTION = ("生图提示词预设Pro:生图预设(空latent类型/输出语言/预设模式/画幅参数 + 8 大类元素面板提示词拼装)" "+ LLM 提示词增强反推(本地模型或在线 API、增强预设、反推预设、追加设定、采样参数、生成后控制)。" - "「启用 LLM 反推」关 = 原预设行为;开 = 图/文 → 提示词。输出:提示词 / 提示词列表 / 提示词设定 / 空latent。") + "「启用 LLM 反推」关 = 原预设行为;开 = 图/文 → 提示词。输出:提示词 / 空latent / 提示词列表 / 提示词设定。") @classmethod def IS_CHANGED(cls, manager_settings="", **kwargs): @@ -313,8 +317,13 @@ class XB_ImagePromptPresetPro: images=images, queue_handler=None, ) - # ④c 设定词**原封不动**加在增强结果最顶端(与未启用 LLM 时的成句规则完全一致) - final_prompt = build_prompt(output_lang, preset_mode, three_view_text, out1 or "") + # ④c 思考过程过滤(模型无视规则、把推理段一起输出时只留最终提示词) + raw_out = out1 or "" + body_out = strip_reasoning(raw_out) if run.get("strip_thinking", True) else raw_out + if body_out != raw_out.strip(): + print(f"[XB-生图预设Pro] 🧠 已过滤思考过程:{len(raw_out)} 字 → {len(body_out)} 字") + # ④d 设定词**原封不动**加在增强结果最顶端(与未启用 LLM 时的成句规则完全一致) + final_prompt = build_prompt(output_lang, preset_mode, three_view_text, body_out) else: # 原「🖼️ 生图提示词预设」行为:直接输出拼装好的提示词 final_prompt = base_prompt @@ -338,7 +347,7 @@ class XB_ImagePromptPresetPro: } if use_llm and next_seed is not None: ui["seed"] = [next_seed] # 前端回写到 manager_settings.llm.run.seed(仅 LLM 采样时才推进种子) - return {"ui": ui, "result": (final_prompt, prompt_list, full_system or "", latent)} + return {"ui": ui, "result": (final_prompt, latent, prompt_list, full_system or "")} NODE_CLASS_MAPPINGS = { diff --git a/nodes_llama_prompt_reverse.py b/nodes_llama_prompt_reverse.py index 8c77733..953840b 100644 --- a/nodes_llama_prompt_reverse.py +++ b/nodes_llama_prompt_reverse.py @@ -23,6 +23,7 @@ CATEGORY: XB-llama import json import os import random +import re import folder_paths from aiohttp import web @@ -141,6 +142,7 @@ DEFAULT_SETTINGS = { "run": { "inference_mode": "one by one", "max_frames": 24, "max_size": 256, "seed": 0, "seed_control": "randomize", "force_offload": False, "save_states": False, + "strip_thinking": True, # 模型把「思考过程 / 推理段」一起输出时,只保留最终提示词 }, # 推理参数(原「⚙️ 推理参数」) "params": { @@ -166,6 +168,88 @@ _LANG_HINT = { "英文 [EN]": "【Output language】Always respond in English.", } +# 「只输出最终提示词」硬规则:拼在系统提示词最后,专治模型把思考过程/步骤/说明一起输出 +OUTPUT_FORMAT_HINT = ( + "【输出格式(最高优先级,凌驾于以上所有说明)】\n" + "直接输出最终提示词本身,不要输出思考过程、推理步骤、分析、工作流程、小标题、编号列表、" + "对以上说明的复述、代码块围栏(```),也不要任何开场白、总结或解释。" +) + +# ── 思考过程过滤(模型无视上面的规则、把推理段一起吐出来时的兜底)────────────── +_THINK_BLOCK_RE = re.compile(r"<(thinking|think|analysis|reasoning)\b[^>]*>.*?", re.S | re.I) +_THINK_CLOSE_RE = re.compile(r"", re.I) +_THINK_HEADERS = ("thinking process", "think process", "reasoning process", "chain of thought", + "思考过程", "推理过程", "分析过程", "思维过程", "思路如下", "先分析") +_ANSWER_MARKERS = ("final output", "final answer", "final prompt", + "最终输出", "最终答案", "最终提示词", "最终结果", "成品提示词", "输出结果") + + +def _looks_like_reasoning(block): + """判断一个段落块像「推理 / 草稿 / 清单」而不是成品提示词""" + b = block.strip() + if not b: + return False + if b.startswith(("*", "_", "-", ">", "#", "```", "|")): + return True + if re.match(r"^\**(?:\d+[\.\、\))]|[-*•])\s*\**", b): + return True + if "**" in b and len(b) < 400: + return True + if re.match(r"^\**[^。!?\n]{0,30}[::]\**\s*$", b): + return True + return False + + +def strip_reasoning(text): + """丢掉模型输出里的「思考过程」,只留最终提示词。 + + ① 显式思考块 … / … → 删;只有闭合标签 → 取其后的内容 + ② 显式「最终输出 / Final Output」标记 → 取最后一个标记之后的正文 + ③ 开头是思考标题(Thinking Process / 思考过程…)→ 从尾部往回收集「成品段落」,丢掉前面的推理段 + ④ 去掉整段代码围栏,并去掉首尾空白 + 任何一步拿不到内容都回退原文(绝不把结果清空)。 + """ + raw = text if isinstance(text, str) else "" + s = raw.strip() + if not s: + return raw + original = s + + s2 = _THINK_BLOCK_RE.sub("", s) # ① + closes = list(_THINK_CLOSE_RE.finditer(s2)) + if closes: + s2 = s2[closes[-1].end():] + s = s2.strip() or s + + low = s.lower() # ② + best, hit_len = -1, 0 + for marker in _ANSWER_MARKERS: + i = low.rfind(marker) + if i > best: + best, hit_len = i, len(marker) + if best >= 0: + tail = s[best + hit_len:] + nl = tail.find("\n") + if nl >= 0: + tail = tail[nl + 1:] + tail = tail.lstrip("::  \t-—").strip() # 支持「最终输出:xxx」同行写法 + if tail: + s = tail + + head = s[:400].lower() # ③ + if any(h in head for h in _THINK_HEADERS): + blocks = [b for b in re.split(r"\n\s*\n", s) if b.strip()] + keep = [] + for b in reversed(blocks): + if _looks_like_reasoning(b): + break + keep.append(b) + if keep: + s = "\n\n".join(reversed(keep)).strip() + + s = re.sub(r"^```[a-zA-Z]*\s*|\s*```$", "", s.strip()).strip() # ④ + return s or original + def _num(value, default, lo=None, hi=None): try: @@ -237,6 +321,7 @@ def parse_settings(raw): rd["seed_control"] = _pick(r.get("seed_control"), _SEED_MODES, rd["seed_control"]) rd["force_offload"] = _bool(r.get("force_offload"), rd["force_offload"]) rd["save_states"] = _bool(r.get("save_states"), rd["save_states"]) + rd["strip_thinking"] = _bool(r.get("strip_thinking"), rd["strip_thinking"]) p = data.get("params") if isinstance(data.get("params"), dict) else {} pd = out["params"]