import { app } from "../../../scripts/app.js"; import { PROMPT_LIBRARY } from "./promptLibrary.data.js"; // ── 适用节点 ────────────────────────────────────────────────────────────── const TARGET_NODES = ["K3Video", "K3MotionControl"]; const STYLE_ID = "o1key-plib-styles"; const DIALOG_ID = "o1key-plib-dialog"; // 记录每个节点最近聚焦的提示词框 widget 名 const lastFocused = new WeakMap(); let dialogRoot = null; let activeNode = null; // 单选分类的当前选择:{ [分类名]: 词条label };多选分类:Set const selected = {}; // 单选 const multiSelected = {}; // 多选 → Set const CSS = ` #o1key-plib-dialog{position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.52);color:#ddd;font-family:inherit} #o1key-plib-modal{width:min(460px,calc(100vw - 32px));height:min(760px,calc(100vh - 48px));display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(255,255,255,.12);border-radius:10px;background:var(--comfy-menu-bg,#1a1a1a);box-shadow:0 24px 80px rgba(0,0,0,.5)} #o1key-plib-header{height:50px;padding:0 16px;border-bottom:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-between;flex-shrink:0} #o1key-plib-title{font-size:15px;font-weight:700;color:#eee} .o1pl-icon-btn{width:30px;height:30px;border:1px solid rgba(255,255,255,.12);border-radius:6px;background:rgba(255,255,255,.04);color:#aaa;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s} .o1pl-icon-btn:hover{color:#eee;background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.2)} #o1pl-body{flex:1;min-height:0;overflow:auto;padding:14px 16px} #o1pl-body::-webkit-scrollbar{width:6px} #o1pl-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:3px} .o1pl-cat{margin-bottom:18px} .o1pl-cat-title{font-size:13px;font-weight:700;color:#cfcfcf;margin-bottom:10px;display:flex;align-items:center;gap:6px} .o1pl-cat-multi{font-size:11px;font-weight:400;color:#777} .o1pl-tags{display:flex;flex-wrap:wrap;gap:8px} .o1pl-tag{padding:7px 14px;border:1px solid rgba(255,255,255,.16);border-radius:18px;background:rgba(255,255,255,.03);color:#ccc;font-size:13px;cursor:pointer;transition:all .12s;font-family:inherit} .o1pl-tag:hover{background:rgba(255,255,255,.08);color:#fff} .o1pl-tag.active{border-color:#4ade80;color:#4ade80;background:rgba(74,222,128,.08)} #o1pl-footer{flex-shrink:0;border-top:1px solid rgba(255,255,255,.08);padding:10px 16px;display:flex;flex-direction:column;gap:8px} #o1pl-preview{font-size:12px;color:#9a9a9a;line-height:1.5;max-height:54px;overflow:auto;min-height:18px} #o1pl-preview::-webkit-scrollbar{width:5px} #o1pl-preview::-webkit-scrollbar-thumb{background:rgba(255,255,255,.14);border-radius:3px} #o1pl-footer-row{display:flex;gap:8px;align-items:center} #o1pl-target{flex:1;font-size:11px;color:#777;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .o1pl-btn{padding:8px 18px;border-radius:6px;font-size:13px;cursor:pointer;border:1px solid transparent;font-family:inherit;font-weight:600} .o1pl-btn-ghost{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.14);color:#bbb} .o1pl-btn-ghost:hover{background:rgba(255,255,255,.1);color:#eee} .o1pl-btn-primary{background:#fff;color:#111} .o1pl-btn-primary:hover{background:#e6e6e6} `; function injectStyles() { if (document.getElementById(STYLE_ID)) return; const el = document.createElement("style"); el.id = STYLE_ID; el.textContent = CSS; document.head.appendChild(el); } function escapeHtml(v) { return String(v ?? "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } function iconClose() { return ``; } // ── 提示词框定位 ────────────────────────────────────────────────────────── // 匹配任意「…提示词…」框,但排除「负向提示词 / negative」。 // 用包含匹配(而非前缀),以兼容 DynamicCombo 可能给子输入加的前缀名。 function isPromptWidget(w) { if (!w || typeof w.name !== "string") return false; const n = w.name; if (!n.includes("提示词") && !/prompt/i.test(n)) return false; if (n.includes("负向") || /negative/i.test(n)) return false; return true; } function listPromptWidgets(node) { return (node?.widgets || []).filter(isPromptWidget); } // 取目标提示词框:优先最近聚焦,否则首个「正向/主提示词」,再退首个可用 function resolveTargetWidget(node) { const widgets = listPromptWidgets(node); if (!widgets.length) return null; const remembered = lastFocused.get(node); if (remembered) { const hit = widgets.find(w => w.name === remembered); if (hit) return hit; } return widgets.find(w => w.name.includes("正向") || w.name === "提示词") || widgets[0]; } // 监听节点内提示词框聚焦,记录最近编辑的那个 function trackFocus(node) { for (const w of listPromptWidgets(node)) { const el = w.element || w.inputEl; if (!el || el.dataset?.o1plFocusBound) continue; el.dataset.o1plFocusBound = "1"; el.addEventListener("focus", () => lastFocused.set(node, w.name)); } } function setWidgetValue(node, widget, value) { widget.value = value; const el = widget.element || widget.inputEl; if (el && "value" in el) el.value = value; widget.callback?.(value); node.setDirtyCanvas?.(true, true); } // 智能逗号拼接:自动在已有文本后补「,」 function appendToPrompt(node, widget, addition) { const cur = String(widget.value || "").trim(); let next; if (!cur) { next = addition; } else { const sep = /[,,。.\s]$/.test(cur) ? "" : ","; next = cur + sep + addition; } setWidgetValue(node, widget, next); } // ── 选择状态 → 组装短句 ─────────────────────────────────────────────────── function buildAssembled() { const parts = []; for (const cat of PROMPT_LIBRARY) { if (cat.multi) { const set = multiSelected[cat.name]; if (!set || !set.size) continue; for (const tag of cat.tags) if (set.has(tag.label)) parts.push(tag.prompt); } else { const lbl = selected[cat.name]; if (!lbl) continue; const tag = cat.tags.find(t => t.label === lbl); if (tag) parts.push(tag.prompt); } } return parts.join(","); } function refreshPreview() { const el = dialogRoot?.querySelector("#o1pl-preview"); if (!el) return; const text = buildAssembled(); el.textContent = text || "点击词条组合提示词,预览将显示在这里…"; el.style.color = text ? "#cfcfcf" : "#666"; const tgt = dialogRoot?.querySelector("#o1pl-target"); if (tgt && activeNode) { const w = resolveTargetWidget(activeNode); tgt.textContent = w ? `→ 插入到「${w.name}」` : "(未找到提示词框)"; } } // ── 渲染:灵感词库 tab ──────────────────────────────────────────────────── function renderLibraryTab() { const body = dialogRoot.querySelector("#o1pl-body"); body.innerHTML = PROMPT_LIBRARY.map(cat => { const tags = cat.tags.map(tag => { let active; if (cat.multi) active = multiSelected[cat.name]?.has(tag.label); else active = selected[cat.name] === tag.label; return ``; }).join(""); const multiHint = cat.multi ? `(多选)` : ""; return `