完善节点
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 33 KiB |
@@ -32,7 +32,7 @@ app.registerExtension({
|
||||
const el = wDur.inputEl || wDur.element;
|
||||
if (el) {
|
||||
el.readOnly = true;
|
||||
el.style.backgroundColor = "#1a1a1a";
|
||||
el.style.backgroundColor = "var(--xb-bg)";
|
||||
el.style.color = "#00E676";
|
||||
el.style.textAlign = "center";
|
||||
el.style.fontWeight = "bold";
|
||||
|
||||
@@ -31,7 +31,7 @@ app.registerExtension({
|
||||
const el = wDur.inputEl || wDur.element;
|
||||
if (el) {
|
||||
el.readOnly = true;
|
||||
el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -56,14 +56,14 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const ctr = document.createElement("div");
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
|
||||
const audioEl = document.createElement("audio");
|
||||
audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;";
|
||||
ctr.appendChild(audioEl);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;";
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;";
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = 1200 * dpr; canvas.height = 80 * dpr;
|
||||
ctr.appendChild(canvas);
|
||||
|
||||
@@ -30,7 +30,7 @@ app.registerExtension({
|
||||
|
||||
// Nodes 2.0:显示型字段走 options.read_only(Vue 部件没有 element 可改)
|
||||
if (wTot && wTot.options) wTot.options.read_only = true;
|
||||
if (wTot) setTimeout(() => { const el = wTot.inputEl || wTot.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100);
|
||||
if (wTot) setTimeout(() => { const el = wTot.inputEl || wTot.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100);
|
||||
|
||||
const audioEl1 = document.createElement("audio"); audioEl1.controls = true; audioEl1.style.cssText = "width:100%;height:30px;outline:none;";
|
||||
const audioEl2 = document.createElement("audio"); audioEl2.controls = true; audioEl2.style.cssText = "width:100%;height:30px;outline:none;";
|
||||
@@ -39,7 +39,7 @@ app.registerExtension({
|
||||
let drags = { 1: null, 2: null }; // 存储双轨的拖动状态
|
||||
|
||||
const ctr = document.createElement("div");
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"4px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"4px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
|
||||
const mkUpload = (w, label) => {
|
||||
const fi = document.createElement("input"); fi.type = "file"; fi.accept = "audio/*,video/*"; fi.style.display = "none";
|
||||
@@ -59,12 +59,12 @@ app.registerExtension({
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const label1 = document.createElement("div"); label1.textContent = "🎵 音频1"; label1.style.cssText = "font-size:11px;color:#4FC3F7;font-weight:bold;";
|
||||
ctr.appendChild(label1); ctr.appendChild(audioEl1);
|
||||
const canvas1 = document.createElement("canvas"); canvas1.style.cssText = "width:100%;height:36px;border-radius:3px;background:#111;cursor:pointer;display:block;";
|
||||
const canvas1 = document.createElement("canvas"); canvas1.style.cssText = "width:100%;height:36px;border-radius:3px;background:var(--xb-inset);cursor:pointer;display:block;";
|
||||
canvas1.width = 1200 * dpr; canvas1.height = 72 * dpr; ctr.appendChild(canvas1);
|
||||
|
||||
const label2 = document.createElement("div"); label2.textContent = "🎵 音频2"; label2.style.cssText = "font-size:11px;color:#F44336;font-weight:bold;";
|
||||
ctr.appendChild(label2); ctr.appendChild(audioEl2);
|
||||
const canvas2 = document.createElement("canvas"); canvas2.style.cssText = "width:100%;height:36px;border-radius:3px;background:#111;cursor:pointer;display:block;";
|
||||
const canvas2 = document.createElement("canvas"); canvas2.style.cssText = "width:100%;height:36px;border-radius:3px;background:var(--xb-inset);cursor:pointer;display:block;";
|
||||
canvas2.width = 1200 * dpr; canvas2.height = 72 * dpr; ctr.appendChild(canvas2);
|
||||
|
||||
const domWidget = node.addDOMWidget("xb3_ui", "custom", ctr);
|
||||
|
||||
@@ -31,7 +31,7 @@ app.registerExtension({
|
||||
[w.mutes1_data, w.mutes2_data].forEach(wd => { hideWidget(node, wd); });
|
||||
if (w.total_display) {
|
||||
if (w.total_display.options) w.total_display.options.read_only = true; // Nodes 2.0
|
||||
setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200);
|
||||
setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200);
|
||||
}
|
||||
|
||||
// 轨道状态机
|
||||
|
||||
@@ -270,7 +270,7 @@ export function ensureBanner(node, name, text, minH = 30) {
|
||||
el.dataset.xbBanner = "1";
|
||||
el.textContent = text;
|
||||
el.style.cssText =
|
||||
"padding:6px 8px;border-radius:6px;background:#2b3138;color:#cfd6dd;" +
|
||||
"padding:6px 8px;border-radius:6px;background:var(--xb-bg-raise);color:var(--xb-text);" +
|
||||
"font-size:12px;line-height:1.4;text-align:center;user-select:none;";
|
||||
w = node.addDOMWidget(name, name, el, { serialize: false, hideOnZoom: false, getMinHeight: () => minH });
|
||||
sizeDomWidget(node, w, minH);
|
||||
|
||||
@@ -73,7 +73,7 @@ app.registerExtension({
|
||||
[w.mutes1_data, w.mutes2_data].forEach(wd => { hideWidget(node, wd); });
|
||||
if (w.total_display) {
|
||||
if (w.total_display.options) w.total_display.options.read_only = true; // Nodes 2.0
|
||||
setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200);
|
||||
setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200);
|
||||
}
|
||||
|
||||
// ── 轨道状态机(来自 xb_audio_slicer_v3.js)──
|
||||
|
||||
@@ -73,7 +73,7 @@ app.registerExtension({
|
||||
const el = wDur.inputEl || wDur.element;
|
||||
if (el) {
|
||||
el.readOnly = true;
|
||||
el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -100,14 +100,14 @@ app.registerExtension({
|
||||
|
||||
// ── 波形可视化 UI(来自 xb_audio_slicer_v1.js)──
|
||||
const ctr = document.createElement("div");
|
||||
Object.assign(ctr.style, { display: "flex", flexDirection: "column", gap: "6px", width: "100%", padding: "6px", boxSizing: "border-box", background: "#161616", borderRadius: "6px", color: "#ccc", fontFamily: "sans-serif", marginTop: "4px" });
|
||||
Object.assign(ctr.style, { display: "flex", flexDirection: "column", gap: "6px", width: "100%", padding: "6px", boxSizing: "border-box", background: "#161616", borderRadius: "6px", color: "var(--xb-text)", fontFamily: "sans-serif", marginTop: "4px" });
|
||||
|
||||
const audioEl = document.createElement("audio");
|
||||
audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;";
|
||||
ctr.appendChild(audioEl);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;";
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;";
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = 1200 * dpr; canvas.height = 80 * dpr;
|
||||
ctr.appendChild(canvas);
|
||||
|
||||
@@ -130,12 +130,12 @@ function mkBtn(text, tip, onClick, css) {
|
||||
b.textContent = text;
|
||||
if (tip) b.title = tip;
|
||||
b.style.cssText = "font-size:11px;line-height:1.2;height:22px;padding:0 8px;border-radius:5px;"
|
||||
+ "border:1px solid #555;background:#2b2b2b;color:#ddd;cursor:pointer;white-space:nowrap;font-family:inherit;"
|
||||
+ "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);cursor:pointer;white-space:nowrap;font-family:inherit;"
|
||||
+ (css || "");
|
||||
b.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||
b.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); onClick?.(e); });
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "#2b2b2b"; });
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-bg-raise)"; });
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -250,14 +250,14 @@ function openPicker(node, kind) {
|
||||
+ "display:flex;overflow:auto;";
|
||||
const panel = document.createElement("div");
|
||||
panel.style.cssText = "width:660px;max-width:94vw;max-height:86vh;margin:auto;flex:0 0 auto;display:flex;flex-direction:column;"
|
||||
+ "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;font:13px/1.55 system-ui,sans-serif;"
|
||||
+ "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);font:13px/1.55 system-ui,sans-serif;"
|
||||
+ "box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;";
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.style.cssText = "padding:12px 16px;background:#232326;border-bottom:1px solid #3a3a3a;display:flex;align-items:center;gap:10px;";
|
||||
head.style.cssText = "padding:12px 16px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);display:flex;align-items:center;gap:10px;";
|
||||
const hTitle = document.createElement("div");
|
||||
hTitle.textContent = isGroups ? "⚙️ 选择要控制的分组" : "⚙️ 选择要控制的节点";
|
||||
hTitle.style.cssText = "flex:1 1 auto;font-size:15px;font-weight:700;color:#eee;";
|
||||
hTitle.style.cssText = "flex:1 1 auto;font-size:15px;font-weight:700;color:var(--xb-text);";
|
||||
const hClose = mkBtn("✕", "关闭", () => close());
|
||||
head.append(hTitle, hClose);
|
||||
|
||||
@@ -266,10 +266,10 @@ function openPicker(node, kind) {
|
||||
const search = document.createElement("input");
|
||||
search.type = "text";
|
||||
search.placeholder = isGroups ? "搜索分组…" : "搜索节点名 / 类型…";
|
||||
search.style.cssText = "flex:1 1 220px;min-width:160px;height:26px;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:5px;padding:0 8px;font-size:12px;font-family:inherit;";
|
||||
search.style.cssText = "flex:1 1 220px;min-width:160px;height:26px;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:5px;padding:0 8px;font-size:12px;font-family:inherit;";
|
||||
const countEl = document.createElement("span");
|
||||
countEl.style.cssText = "color:#8a8a8a;font-size:12px;font-variant-numeric:tabular-nums;";
|
||||
countEl.style.cssText = "color:var(--xb-text-dim);font-size:12px;font-variant-numeric:tabular-nums;";
|
||||
const hintEl = document.createElement("span");
|
||||
hintEl.textContent = "⠿ 拖拽可调整顺序";
|
||||
hintEl.style.cssText = "color:#6f7883;font-size:11px;";
|
||||
@@ -279,7 +279,7 @@ function openPicker(node, kind) {
|
||||
list.style.cssText = "flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:3px;";
|
||||
|
||||
const foot = document.createElement("div");
|
||||
foot.style.cssText = "padding:10px 16px;border-top:1px solid #3a3a3a;display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;background:#18181a;";
|
||||
foot.style.cssText = "padding:10px 16px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;background:var(--xb-bg-soft);";
|
||||
|
||||
const close = () => { try { overlay.remove(); } catch (_) { /* ignore */ } };
|
||||
|
||||
@@ -319,7 +319,7 @@ function openPicker(node, kind) {
|
||||
ghost.setAttribute("data-xb-ghost", "1");
|
||||
ghost.style.cssText += `;position:fixed;left:${srcRect.left}px;top:${srcRect.top}px;width:${srcRect.width}px;`
|
||||
+ "box-sizing:border-box;z-index:10360;pointer-events:none;margin:0;"
|
||||
+ "background:#33343a;border:1px solid #f59e0b;border-radius:6px;"
|
||||
+ "background:var(--xb-bg-raise);border:1px solid #f59e0b;border-radius:6px;"
|
||||
+ "box-shadow:0 14px 30px rgba(0,0,0,.65),0 0 0 1px rgba(245,158,11,.25);"
|
||||
+ "transform:scale(1.02);opacity:.98;";
|
||||
document.body.append(ghost);
|
||||
@@ -348,7 +348,7 @@ function openPicker(node, kind) {
|
||||
panel.style.height = "";
|
||||
src.style.opacity = "1";
|
||||
src.style.borderStyle = "solid";
|
||||
src.style.borderColor = "#333";
|
||||
src.style.borderColor = "var(--xb-border)";
|
||||
};
|
||||
const onMove = (e) => {
|
||||
if (!dragKey) return;
|
||||
@@ -411,7 +411,7 @@ function openPicker(node, kind) {
|
||||
shown++;
|
||||
const row = document.createElement("label");
|
||||
row.style.cssText = "display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;cursor:pointer;"
|
||||
+ "background:#242424;border:1px solid #333;";
|
||||
+ "background:var(--xb-bg-raise);border:1px solid var(--xb-border);";
|
||||
row.dataset.xbRow = it.key;
|
||||
const grip = document.createElement("span");
|
||||
grip.textContent = "⠿";
|
||||
@@ -444,7 +444,7 @@ function openPicker(node, kind) {
|
||||
if (!shown) {
|
||||
const empty = document.createElement("div");
|
||||
empty.textContent = isGroups ? "当前画布上没有分组" : "没有匹配的节点";
|
||||
empty.style.cssText = "padding:16px;color:#8a8a8a;text-align:center;";
|
||||
empty.style.cssText = "padding:16px;color:var(--xb-text-dim);text-align:center;";
|
||||
list.append(empty);
|
||||
}
|
||||
syncCount();
|
||||
@@ -481,7 +481,7 @@ function openPicker(node, kind) {
|
||||
};
|
||||
|
||||
const cancel = mkBtn("取消", "放弃修改", close);
|
||||
const ok = mkBtn("确定", "保存选择", confirm, "border-color:#3f86d0;background:#22456b;color:#eaf4ff;font-weight:600;");
|
||||
const ok = mkBtn("确定", "保存选择", confirm, "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);font-weight:600;");
|
||||
foot.append(
|
||||
mkBtn("全选", "选中当前筛选结果里的全部条目", () => selectAll(true)),
|
||||
mkBtn("清空", "取消全部选择", () => selectAll(false)),
|
||||
@@ -502,7 +502,7 @@ function buildPanel(node, kind) {
|
||||
const isGroups = kind === "groups";
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = "width:100%;height:100%;box-sizing:border-box;display:flex;flex-direction:column;"
|
||||
+ `gap:4px;padding:${PANEL_PAD / 2}px;overflow:visible;background:#2b2b2b;border-radius:6px;`;
|
||||
+ `gap:4px;padding:${PANEL_PAD / 2}px;overflow:visible;background:var(--xb-bg-raise);border-radius:6px;`;
|
||||
|
||||
// 节点表面只留「开关列表」——选择 / 禁用方式 / 切换模式 全部收进右键菜单
|
||||
const list = document.createElement("div");
|
||||
@@ -615,7 +615,7 @@ function toggleRow(node, index) {
|
||||
function buildSwitch(enabled, disabled) {
|
||||
const box = document.createElement("span");
|
||||
box.style.cssText = "flex:0 0 auto;position:relative;width:34px;height:18px;border-radius:9px;"
|
||||
+ `background:${disabled ? "#3a3a3a" : enabled ? "#3f86d0" : "#4a4a4a"};`
|
||||
+ `background:${disabled ? "var(--xb-border)" : enabled ? "#3f86d0" : "var(--xb-border)"};`
|
||||
+ "transition:background .15s;";
|
||||
const knob = document.createElement("span");
|
||||
knob.style.cssText = "position:absolute;top:2px;width:14px;height:14px;border-radius:7px;background:#fff;"
|
||||
@@ -676,7 +676,7 @@ function refreshRows(node) {
|
||||
? "还没有选择分组 —— "
|
||||
: "还没有选择节点 —— ")
|
||||
+ "右击本节点 →「" + (node.type === TYPE_GROUPS ? "选择分组" : "选择节点") + "…」勾选。";
|
||||
tip.style.cssText = "padding:10px;color:#8a8a8a;font-size:11px;line-height:1.6;";
|
||||
tip.style.cssText = "padding:10px;color:var(--xb-text-dim);font-size:11px;line-height:1.6;";
|
||||
list.append(tip);
|
||||
applyPanelHeight(node, true);
|
||||
return;
|
||||
@@ -689,7 +689,7 @@ function refreshRows(node) {
|
||||
const el = document.createElement("div");
|
||||
el.style.cssText = "display:flex;align-items:center;gap:8px;padding:3px 6px;border-radius:5px;"
|
||||
+ `background:${enabled ? "#22456b" : "#1f1f1f"};`
|
||||
+ `border:1px solid ${enabled ? "#3f86d0" : "#3a3a3a"};`
|
||||
+ `border:1px solid ${enabled ? "#3f86d0" : "var(--xb-border)"};`
|
||||
+ "cursor:pointer;";
|
||||
|
||||
const name = document.createElement("span");
|
||||
@@ -707,7 +707,7 @@ function refreshRows(node) {
|
||||
if (!ok) {
|
||||
// 永远保持一个开启:拒绝全关 → 闪一下提示
|
||||
el.style.borderColor = "#e05555";
|
||||
setTimeout(() => { try { el.style.borderColor = enabled ? "#3f86d0" : "#3a3a3a"; } catch (_) { /* ignore */ } }, 450);
|
||||
setTimeout(() => { try { el.style.borderColor = enabled ? "#3f86d0" : "var(--xb-border)"; } catch (_) { /* ignore */ } }, 450);
|
||||
}
|
||||
});
|
||||
el.addEventListener("mouseenter", () => { el.style.filter = "brightness(1.18)"; });
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* · 普通图片文件夹(无清单):按文件名多宫格 + 搜索 + 排序 + 加载更多;
|
||||
* · 单选(一个端口一张图)→ 回填节点内部字段 pose_ref / outfit_ref。
|
||||
*
|
||||
* 视觉沿用本包弹窗风格(#1c1c1e / 880px / 圆角 8px)。
|
||||
* 视觉沿用本包弹窗风格(var(--xb-bg) / 880px / 圆角 8px)。
|
||||
*/
|
||||
|
||||
import { api } from "../../scripts/api.js";
|
||||
@@ -164,25 +164,25 @@ function shell(title) {
|
||||
const mask = el("div", "position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,.78);"
|
||||
+ "display:flex;overflow:auto;box-sizing:border-box;");
|
||||
const card = el("div", "margin:auto;flex:0 0 auto;display:flex;flex-direction:column;"
|
||||
+ "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;"
|
||||
+ "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);"
|
||||
+ "font-family:system-ui,sans-serif;line-height:1.65;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;");
|
||||
const head = el("div", "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;"
|
||||
const head = el("div", "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);"
|
||||
+ "display:flex;align-items:center;gap:12px;flex-wrap:wrap;");
|
||||
head.append(el("span", "flex:1 1 320px;min-width:200px;font-size:1.2em;font-weight:700;color:#eee;", title));
|
||||
head.append(el("span", "flex:1 1 320px;min-width:200px;font-size:1.2em;font-weight:700;color:var(--xb-text);", title));
|
||||
|
||||
// 顶部常驻条(放「禁用」勾选框等,切换视图时不会被清空)
|
||||
const top = el("div", "padding:8px 18px;background:#202024;border-bottom:1px solid #333;"
|
||||
const top = el("div", "padding:8px 18px;background:var(--xb-bg);border-bottom:1px solid var(--xb-border);"
|
||||
+ "display:flex;align-items:center;gap:12px;flex-wrap:wrap;");
|
||||
top.hidden = true;
|
||||
|
||||
const body = el("div", "padding:14px 18px 16px;display:flex;flex-direction:column;gap:10px;overflow:auto;min-height:0;");
|
||||
const foot = el("div", "padding:10px 18px;border-top:1px solid #3a3a3a;background:#18181a;"
|
||||
const foot = el("div", "padding:10px 18px;border-top:1px solid var(--xb-border);background:var(--xb-bg-soft);"
|
||||
+ "display:flex;align-items:center;gap:10px;flex-wrap:wrap;");
|
||||
|
||||
// 顶部工具条:字号 A−/A+ | 界面 −/%/+ | ✕
|
||||
const tools = el("span", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;");
|
||||
const fontVal = el("span", "min-width:26px;text-align:center;color:#e8a87c;font-variant-numeric:tabular-nums;", String(fontGet()));
|
||||
const zoomVal = el("span", "min-width:46px;text-align:center;color:#ffd98a;font-variant-numeric:tabular-nums;",
|
||||
const fontVal = el("span", "min-width:26px;text-align:center;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(fontGet()));
|
||||
const zoomVal = el("span", "min-width:46px;text-align:center;color:var(--xb-warn);font-variant-numeric:tabular-nums;",
|
||||
Math.round(zoomGet() * 100) + "%");
|
||||
const refreshVals = () => {
|
||||
fontVal.textContent = String(fontGet());
|
||||
@@ -193,30 +193,30 @@ function shell(title) {
|
||||
applyScale(card);
|
||||
try { card.dispatchEvent(new Event("xb-scale")); } catch (_) {}
|
||||
};
|
||||
const fontMinus = mkBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;height:1.9em;padding:0 .55em;", () => {
|
||||
const fontMinus = mkBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);height:1.9em;padding:0 .55em;", () => {
|
||||
fontSet(fontGet() - 1);
|
||||
scaleNow();
|
||||
refreshVals();
|
||||
});
|
||||
const fontPlus = mkBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;height:1.9em;padding:0 .55em;", () => {
|
||||
const fontPlus = mkBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);height:1.9em;padding:0 .55em;", () => {
|
||||
fontSet(fontGet() + 1);
|
||||
scaleNow();
|
||||
refreshVals();
|
||||
});
|
||||
const zoomMinus = mkBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;height:1.9em;padding:0 .6em;", () => {
|
||||
const zoomMinus = mkBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);height:1.9em;padding:0 .6em;", () => {
|
||||
zoomSet(zoomGet() - ZOOM_STEP);
|
||||
scaleNow();
|
||||
refreshVals();
|
||||
});
|
||||
const zoomPlus = mkBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;height:1.9em;padding:0 .6em;", () => {
|
||||
const zoomPlus = mkBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);height:1.9em;padding:0 .6em;", () => {
|
||||
zoomSet(zoomGet() + ZOOM_STEP);
|
||||
scaleNow();
|
||||
refreshVals();
|
||||
});
|
||||
tools.append(
|
||||
el("span", "color:#8a8a8a;font-size:.9em;", "字号"), fontMinus, fontVal, fontPlus,
|
||||
el("span", "color:var(--xb-text-dim);font-size:.9em;", "字号"), fontMinus, fontVal, fontPlus,
|
||||
el("span", "color:#5a5a5a;", "|"),
|
||||
el("span", "color:#8a8a8a;font-size:.9em;", "界面"), zoomMinus, zoomVal, zoomPlus,
|
||||
el("span", "color:var(--xb-text-dim);font-size:.9em;", "界面"), zoomMinus, zoomVal, zoomPlus,
|
||||
);
|
||||
head.append(tools);
|
||||
|
||||
@@ -235,7 +235,7 @@ function shell(title) {
|
||||
scaleNow();
|
||||
refreshVals();
|
||||
};
|
||||
tools.append(mkBtn("✕", "border:1px solid #555;background:#2b2b2b;color:#ddd;height:1.9em;padding:0 .7em;", close));
|
||||
tools.append(mkBtn("✕", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:1.9em;padding:0 .7em;", close));
|
||||
|
||||
card.append(head, top, body, foot);
|
||||
mask.append(card);
|
||||
@@ -290,23 +290,23 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
disableBox.type = "checkbox";
|
||||
disableBox.checked = disabled;
|
||||
disableBox.id = `xb-ref-disable-${kind}`;
|
||||
const disableText = el("span", "color:#ddd;cursor:pointer;user-select:none;", `🚫 禁用此参考图(不做任何输出)`);
|
||||
const disableText = el("span", "color:var(--xb-text);cursor:pointer;user-select:none;", `🚫 禁用此参考图(不做任何输出)`);
|
||||
const disableWrap = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;padding:2px 0;");
|
||||
disableWrap.append(disableBox, disableText);
|
||||
const HINT_OFF = "勾选后本参考图不做任何输出(等同 mute):接在它下游的节点会被直接跳过,不执行也不报错。";
|
||||
const HINT_ON = `已禁用:${kindName}图不做任何输出,下游节点会被跳过。取消勾选即恢复。`;
|
||||
const disableHint = el("span", "color:#8a8a8a;font-size:.88em;", HINT_OFF);
|
||||
const disableHint = el("span", "color:var(--xb-text-dim);font-size:.88em;", HINT_OFF);
|
||||
ui.top.append(disableWrap, disableHint);
|
||||
ui.top.hidden = false;
|
||||
disableBox.addEventListener("change", () => {
|
||||
disabled = disableBox.checked;
|
||||
disableText.style.color = disabled ? "#ffb74d" : "#ddd";
|
||||
disableHint.style.color = disabled ? "#d9a441" : "#8a8a8a";
|
||||
disableText.style.color = disabled ? "#ffb74d" : "var(--xb-text)";
|
||||
disableHint.style.color = disabled ? "#d9a441" : "var(--xb-text-dim)";
|
||||
disableHint.textContent = disabled ? HINT_ON : HINT_OFF;
|
||||
emit(null, false); // 立即写回节点(不关窗,方便接着选图)
|
||||
});
|
||||
disableHint.textContent = disabled ? HINT_ON : HINT_OFF;
|
||||
disableText.style.color = disabled ? "#ffb74d" : "#ddd";
|
||||
disableText.style.color = disabled ? "#ffb74d" : "var(--xb-text)";
|
||||
|
||||
// ── ① 绑定界面(未绑定 / 未确认时强制先绑定) ──────────────────
|
||||
async function showBinding(settings, message = "") {
|
||||
@@ -314,22 +314,22 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
ui.foot.replaceChildren();
|
||||
const cfg = (settings && settings[kind]) || {};
|
||||
|
||||
ui.body.append(el("div", "color:#ffb74d;line-height:1.75;white-space:pre-wrap;",
|
||||
ui.body.append(el("div", "color:var(--xb-warn);line-height:1.75;white-space:pre-wrap;",
|
||||
`⚠️ 首次使用需手动绑定${kind === "pose" ? "姿态" : "穿搭"}参考图库目录。\n`
|
||||
+ "· 整套图库:选择包含 data / img / thumbs 的目录"
|
||||
+ "(目录里有 data/*.json 素材清单即可自动识别)\n"
|
||||
+ "· 普通文件夹:任意装图片的目录即可(按文件名多宫格显示)\n"
|
||||
+ "· 本节点不内置、不分发任何图库素材;请自行确认素材的合法来源与使用授权。"));
|
||||
|
||||
const pathInput = el("input", "flex:1 1 320px;min-width:220px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;");
|
||||
const pathInput = el("input", "flex:1 1 320px;min-width:220px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;");
|
||||
pathInput.type = "text";
|
||||
pathInput.value = cfg.directory || "";
|
||||
pathInput.placeholder = "填写 ComfyUI 所在电脑上的绝对路径";
|
||||
|
||||
const status = el("div", "color:#e07070;min-height:1.4em;white-space:pre-wrap;", message);
|
||||
|
||||
const browse = mkBtn("📂 浏览本机目录", "border:1px solid #555;background:#2b2b2b;color:#ddd;height:2.2em;padding:0 12px;", async () => {
|
||||
const browse = mkBtn("📂 浏览本机目录", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:2.2em;padding:0 12px;", async () => {
|
||||
try {
|
||||
const resp = await api.fetchApi("/xb_toolbox/choose_folder", { method: "POST" });
|
||||
const data = await resp.json();
|
||||
@@ -357,7 +357,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
agreeRow.append(agree, el("span", "",
|
||||
"我已确认合法获得该图库的使用权,并自行承担素材使用与合规责任(本插件只读取本机文件,不上传、不分发)。"));
|
||||
|
||||
const save = mkBtn("✅ 检查并绑定", "background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", async () => {
|
||||
const save = mkBtn("✅ 检查并绑定", "background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", async () => {
|
||||
save.disabled = true;
|
||||
status.style.color = "#bbb";
|
||||
status.textContent = "正在检查目录…";
|
||||
@@ -381,12 +381,12 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
});
|
||||
|
||||
ui.body.append(row, agreeRow, status);
|
||||
ui.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "绑定信息只保存在本机 ComfyUI 用户目录,不写进工作流。"), save);
|
||||
ui.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "绑定信息只保存在本机 ComfyUI 用户目录,不写进工作流。"), save);
|
||||
}
|
||||
|
||||
// ── ② 图库界面 ────────────────────────────────────────────────
|
||||
async function showGallery(settings, message = "") {
|
||||
ui.body.replaceChildren(el("div", "color:#bbb;", "正在读取图库…"));
|
||||
ui.body.replaceChildren(el("div", "color:var(--xb-text-dim);", "正在读取图库…"));
|
||||
ui.foot.replaceChildren();
|
||||
|
||||
let cat;
|
||||
@@ -406,19 +406,19 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
|
||||
// ── 筛选栏 ──
|
||||
const bar = el("div", "display:flex;gap:8px;align-items:center;flex-wrap:wrap;");
|
||||
const search = el("input", "flex:1 1 240px;min-width:160px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;");
|
||||
const search = el("input", "flex:1 1 240px;min-width:160px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;");
|
||||
search.type = "search";
|
||||
search.placeholder = kind === "pose" ? "搜索姿势、编号、体系、分类、角度…" : "搜索款式、颜色、关键词、编号…";
|
||||
bar.append(search);
|
||||
|
||||
const makeSelect = (caption, values, allLabel, onChange) => {
|
||||
const select = el("select", "height:2em;background:#1e1e1e;color:#ddd;border:1px solid #444;"
|
||||
const select = el("select", "height:2em;background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);"
|
||||
+ "border-radius:4px;padding:0 6px;font-size:1em;");
|
||||
select.add(new Option(allLabel, ""));
|
||||
for (const value of values) select.add(new Option(value, value));
|
||||
select.addEventListener("change", () => onChange(select.value));
|
||||
const wrap = el("label", "display:flex;gap:6px;align-items:center;color:#aaa;white-space:nowrap;");
|
||||
const wrap = el("label", "display:flex;gap:6px;align-items:center;color:var(--xb-text-dim);white-space:nowrap;");
|
||||
wrap.append(el("span", "", caption), select);
|
||||
return { wrap, select };
|
||||
};
|
||||
@@ -451,7 +451,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
if ((facets.tag || []).length) bar.append(tagSel.wrap);
|
||||
} else {
|
||||
bar.append(catSel.wrap);
|
||||
const sortSel = el("select", "height:2em;background:#1e1e1e;color:#ddd;border:1px solid #444;"
|
||||
const sortSel = el("select", "height:2em;background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);"
|
||||
+ "border-radius:4px;padding:0 6px;font-size:1em;");
|
||||
sortSel.add(new Option("文件名 ↑", "name"));
|
||||
sortSel.add(new Option("文件名 ↓", "name-desc"));
|
||||
@@ -459,7 +459,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
bar.append(sortSel);
|
||||
}
|
||||
|
||||
bar.append(mkBtn("重置筛选", "border:1px solid #555;background:transparent;color:#ccc;height:2em;padding:0 10px;", () => {
|
||||
bar.append(mkBtn("重置筛选", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text);height:2em;padding:0 10px;", () => {
|
||||
Object.assign(state, { query: "", system: "", category: "", angle: "", tag: "", collection: "all" });
|
||||
search.value = "";
|
||||
sysSel.select.value = "";
|
||||
@@ -474,7 +474,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
const tabButtons = [];
|
||||
if (isLibrary) {
|
||||
for (const [key, text] of [["all", "全部"], ["favorites", "♡ 收藏"], ["recent", "最近使用"]]) {
|
||||
const btn = mkBtn(text, "border:1px solid #555;background:#242424;color:#ccc;height:2em;padding:0 10px;", () => {
|
||||
const btn = mkBtn(text, "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:2em;padding:0 10px;", () => {
|
||||
state.collection = key;
|
||||
render();
|
||||
});
|
||||
@@ -483,7 +483,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
tabs.append(btn);
|
||||
}
|
||||
if (canGroup) {
|
||||
tabs.append(mkBtn("切换分组 / 逐张", "border:1px solid #555;background:transparent;color:#bbb;height:2em;padding:0 10px;", () => {
|
||||
tabs.append(mkBtn("切换分组 / 逐张", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2em;padding:0 10px;", () => {
|
||||
state.grouped = !state.grouped;
|
||||
render();
|
||||
}));
|
||||
@@ -492,11 +492,11 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
// 不能分组的库(没有姿势编号)不显示「切换分组」按钮,state.grouped 一并置 false
|
||||
if (!canGroup) state.grouped = false;
|
||||
|
||||
const summary = el("div", "color:#9fc3e8;font-size:.95em;");
|
||||
const summary = el("div", "color:var(--xb-info);font-size:.95em;");
|
||||
const notice = el("div", "color:#e07070;min-height:1.2em;");
|
||||
notice.textContent = message || "";
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;align-content:start;");
|
||||
const more = mkBtn("加载更多", "border:1px dashed #555;background:transparent;color:#bbb;height:2.2em;grid-column:1/-1;", () => {
|
||||
const more = mkBtn("加载更多", "border:1px dashed var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2.2em;grid-column:1/-1;", () => {
|
||||
state.shown += PAGE;
|
||||
render(false);
|
||||
});
|
||||
@@ -514,10 +514,10 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
|
||||
// ── 卡片 ──
|
||||
function makeCard(item, groupItems) {
|
||||
const card = el("div", "position:relative;border:2px solid " + (selected?.id === item.id ? "#5b9bd5" : "#333")
|
||||
+ ";border-radius:6px;overflow:hidden;background:#222;display:flex;flex-direction:column;");
|
||||
const card = el("div", "position:relative;border:2px solid " + (selected?.id === item.id ? "#5b9bd5" : "var(--xb-border)")
|
||||
+ ";border-radius:6px;overflow:hidden;background:var(--xb-inset);display:flex;flex-direction:column;");
|
||||
// 缩略图用 contain:姿势/服装都要看全,绝不裁切;背景压深色让留白不刺眼
|
||||
const img = el("img", "width:100%;height:9.6em;object-fit:contain;display:block;background:#101012;cursor:zoom-in;");
|
||||
const img = el("img", "width:100%;height:9.6em;object-fit:contain;display:block;background:var(--xb-bg);cursor:zoom-in;");
|
||||
img.loading = "lazy";
|
||||
img.decoding = "async";
|
||||
img.alt = item.name;
|
||||
@@ -537,9 +537,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
}
|
||||
|
||||
const meta = el("div", "padding:6px 8px;font-size:.86em;line-height:1.5;overflow:hidden;");
|
||||
meta.append(el("div", "color:#8fd3ff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", item.name));
|
||||
meta.append(el("div", "color:var(--xb-info);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", item.name));
|
||||
const sub = [item.system, item.category, item.angle].filter(Boolean).join(" · ");
|
||||
if (sub) meta.append(el("div", "color:#8a8a8a;font-size:.92em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", sub));
|
||||
if (sub) meta.append(el("div", "color:var(--xb-text-dim);font-size:.92em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", sub));
|
||||
|
||||
const actions = el("div", "display:flex;gap:5px;padding:0 8px 7px;");
|
||||
const pick = mkBtn(selected?.id === item.id ? "✓ 已选" : "选择",
|
||||
@@ -549,7 +549,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
render(false);
|
||||
});
|
||||
const fav = mkBtn(favorites().includes(item.id) ? "♥" : "♡",
|
||||
"flex:0 0 auto;border:1px solid #555;background:transparent;color:#e89;height:1.9em;font-size:.8em;padding:0 8px;", () => {
|
||||
"flex:0 0 auto;border:1px solid var(--xb-border);background:transparent;color:#e89;height:1.9em;font-size:.8em;padding:0 8px;", () => {
|
||||
const list = favorites();
|
||||
writeLocal(`favorites.${kind}`, list.includes(item.id)
|
||||
? list.filter((id) => id !== item.id)
|
||||
@@ -567,9 +567,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
// 视角条只用单字(正/斜/侧/俯/仰)+ 悬停全名:卡片变窄或字号放大时也绝不换行挤压
|
||||
const angleBtn = mkBtn(angle.slice(0, 1), "flex:1 1 0;min-width:0;font-size:.78em;line-height:1.5;height:1.9em;padding:0;"
|
||||
+ "white-space:nowrap;overflow:hidden;"
|
||||
+ "border:1px solid " + (isCurrent ? "#5b9bd5" : "#444") + ";"
|
||||
+ "background:" + (target ? (isCurrent ? "#2d5a88" : "#2a2a2a") : "transparent") + ";"
|
||||
+ "color:" + (target ? "#ddd" : "#555") + ";", () => {
|
||||
+ "border:1px solid " + (isCurrent ? "#5b9bd5" : "var(--xb-border)") + ";"
|
||||
+ "background:" + (target ? (isCurrent ? "#2d5a88" : "var(--xb-bg-raise)") : "transparent") + ";"
|
||||
+ "color:" + (target ? "var(--xb-text)" : "var(--xb-border)") + ";", () => {
|
||||
if (target) preview(target, groupItems);
|
||||
});
|
||||
angleBtn.title = target ? `${angle} · 点击打开大图` : `${angle} 暂无素材`;
|
||||
@@ -583,7 +583,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
// ── 大图预览 ──
|
||||
function preview(item, groupItems) {
|
||||
const view = shell(`预览 · ${item.name}`);
|
||||
const stage = el("div", "display:flex;justify-content:center;align-items:center;background:#111;"
|
||||
const stage = el("div", "display:flex;justify-content:center;align-items:center;background:var(--xb-inset);"
|
||||
+ "border-radius:6px;overflow:hidden;padding:4px;box-sizing:border-box;");
|
||||
// ⚠️ 只给 max-width / max-height + object-fit:contain:浏览器会按原图比例缩放,
|
||||
// 永远显示完整画面、不会被拉伸变形;两处上限都用**布局像素**(视口高度 ÷ 界面缩放),
|
||||
@@ -614,7 +614,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
[value.system, value.category, value.angle].filter(Boolean).join(" · "),
|
||||
`ID:${value.id}`,
|
||||
].filter(Boolean).join("\n");
|
||||
const info = el("div", "color:#bbb;white-space:pre-wrap;font-size:.95em;line-height:1.6;");
|
||||
const info = el("div", "color:var(--xb-text-dim);white-space:pre-wrap;font-size:.95em;line-height:1.6;");
|
||||
info.textContent = previewLines(item);
|
||||
|
||||
let currentItem = item;
|
||||
@@ -624,8 +624,8 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
if (!isLibrary || !Array.isArray(groupItems) || groupItems.length < 2) return;
|
||||
for (const angle of ANGLE_ORDER) {
|
||||
const target = groupItems.find((value) => value.angle === angle);
|
||||
const btn = mkBtn(angle, "border:1px solid " + (target && target.id === currentItem.id ? "#5b9bd5" : "#444")
|
||||
+ ";background:" + (target ? "#2a2a2a" : "transparent") + ";color:" + (target ? "#ddd" : "#555")
|
||||
const btn = mkBtn(angle, "border:1px solid " + (target && target.id === currentItem.id ? "#5b9bd5" : "var(--xb-border)")
|
||||
+ ";background:" + (target ? "var(--xb-bg-raise)" : "transparent") + ";color:" + (target ? "var(--xb-text)" : "var(--xb-border)")
|
||||
+ ";height:1.9em;padding:0 10px;white-space:nowrap;", () => {
|
||||
if (!target) return;
|
||||
currentItem = target;
|
||||
@@ -640,13 +640,13 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
paintAngles();
|
||||
|
||||
view.body.append(stage, info, angles);
|
||||
const use = mkBtn("使用此图", "background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => {
|
||||
const use = mkBtn("使用此图", "background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => {
|
||||
selected = currentItem;
|
||||
currentPick = currentItem;
|
||||
view.close();
|
||||
render(false);
|
||||
});
|
||||
view.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "Esc 关闭"), use);
|
||||
view.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "Esc 关闭"), use);
|
||||
}
|
||||
|
||||
// ── 渲染 ──
|
||||
@@ -686,9 +686,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
|
||||
for (const btn of tabButtons) {
|
||||
const active = btn.dataset.collection === state.collection;
|
||||
btn.style.background = active ? "#2d5a88" : "#242424";
|
||||
btn.style.color = active ? "#fff" : "#ccc";
|
||||
btn.style.borderColor = active ? "#5b9bd5" : "#555";
|
||||
btn.style.background = active ? "#2d5a88" : "var(--xb-bg-raise)";
|
||||
btn.style.color = active ? "#fff" : "var(--xb-text)";
|
||||
btn.style.borderColor = active ? "#5b9bd5" : "var(--xb-border)";
|
||||
}
|
||||
paintFooter();
|
||||
}
|
||||
@@ -697,24 +697,24 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) {
|
||||
function paintFooter() {
|
||||
ui.foot.replaceChildren();
|
||||
if (selected) {
|
||||
const thumb = el("img", "width:46px;height:46px;object-fit:contain;border-radius:4px;border:1px solid #444;background:#101012;flex:0 0 auto;");
|
||||
const thumb = el("img", "width:46px;height:46px;object-fit:contain;border-radius:4px;border:1px solid var(--xb-border);background:var(--xb-bg);flex:0 0 auto;");
|
||||
thumb.src = imageUrl(kind, selected, true);
|
||||
const label = (selected.group ? `[${selected.group}] ` : "") + selected.name;
|
||||
const text = el("span", "flex:1 1 auto;color:#ddd;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", label);
|
||||
const text = el("span", "flex:1 1 auto;color:var(--xb-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", label);
|
||||
text.title = label;
|
||||
ui.foot.append(thumb, text);
|
||||
} else {
|
||||
ui.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "点卡片上的「选择」或大图里的「使用此图」"));
|
||||
ui.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "点卡片上的「选择」或大图里的「使用此图」"));
|
||||
}
|
||||
const use = mkBtn(disabled ? "✅ 保存(当前已禁用)" : (selected ? "✅ 使用此图" : "请先选一张"),
|
||||
"background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => {
|
||||
"background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => {
|
||||
if (!selected) return;
|
||||
const list = recent();
|
||||
writeLocal(`recent.${kind}`, [selected.id, ...list.filter((id) => id !== selected.id)].slice(0, 80));
|
||||
emit(selected, true);
|
||||
});
|
||||
use.disabled = !selected;
|
||||
const rebind = mkBtn("🔧 更换图库目录", "border:1px solid #555;background:transparent;color:#bbb;height:2.2em;padding:0 10px;", () => {
|
||||
const rebind = mkBtn("🔧 更换图库目录", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2.2em;padding:0 10px;", () => {
|
||||
req("/settings")
|
||||
.then((data) => showBinding(data.settings))
|
||||
.catch((err) => { notice.textContent = err?.message || String(err); });
|
||||
|
||||
@@ -1453,15 +1453,15 @@ function el(tag, css, text) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
|
||||
function makeSectionTitle(text) {
|
||||
return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text);
|
||||
return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text);
|
||||
}
|
||||
|
||||
function field(labelText, control) {
|
||||
const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;");
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
control.style.flex = "1 1 0";
|
||||
control.style.minWidth = "0";
|
||||
g.append(lab, control);
|
||||
@@ -1488,7 +1488,7 @@ function radioRow(options, value, onChange) {
|
||||
for (const o of options) {
|
||||
const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o);
|
||||
const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o);
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = String(value) === val;
|
||||
@@ -1515,7 +1515,7 @@ function textareaControl(value, onChange, css) {
|
||||
}
|
||||
|
||||
function checkboxControl(value, labelText, onChange, tip) {
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;");
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;");
|
||||
const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = !!value;
|
||||
@@ -1577,10 +1577,10 @@ function zoomSync(root) {
|
||||
}
|
||||
function buildZoomRow(root) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
val.classList.add("ipp-zoom-val");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
zoomSet((zoomGet() == null ? 12 : zoomGet()) + d);
|
||||
zoomApplyContainer(root);
|
||||
@@ -1624,10 +1624,10 @@ window.addEventListener("resize", () => {
|
||||
function buildUIRow(target) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面"));
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
val.classList.add("ipp-ui-val");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
uiSet(uiGet() + d * UI_STEP);
|
||||
uiApply(target);
|
||||
@@ -1889,12 +1889,12 @@ function openPanel(ctx, panelId) {
|
||||
const title = `${cat.label} 元素`;
|
||||
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;");
|
||||
const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;");
|
||||
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title);
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title);
|
||||
header.append(titleEl);
|
||||
header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
dialog.append(header);
|
||||
|
||||
// ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里
|
||||
@@ -1903,12 +1903,12 @@ function openPanel(ctx, panelId) {
|
||||
const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;");
|
||||
|
||||
// ── 吸顶预览框(不受滚动条约束,始终在画面内) ──
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;");
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);");
|
||||
const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;");
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
const headCount = el("span", "font-size:11px;color:#888;", "");
|
||||
head.append(headCount);
|
||||
const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;");
|
||||
preview.spellcheck = false;
|
||||
preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…";
|
||||
@@ -1946,10 +1946,10 @@ function openPanel(ctx, panelId) {
|
||||
|
||||
// ── 底部 ──
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
autoCb.type = "checkbox";
|
||||
autoCb.checked = settings.auto_save !== false;
|
||||
@@ -2034,13 +2034,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不
|
||||
* ============================================================ */
|
||||
function openConfirm(title, message, onOk, okText) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "confirm";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} });
|
||||
@@ -2058,29 +2058,29 @@ function openConfirm(title, message, onOk, okText) {
|
||||
* ============================================================ */
|
||||
function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "rel";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;",
|
||||
`道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`));
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
for (const [id, zh, en] of RELATIONS) {
|
||||
const on = id === current;
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`);
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`);
|
||||
b.dataset.rel = id;
|
||||
b.title = `${zh} / ${en}`;
|
||||
b.append(el("span", "font-size:13px;line-height:1.2;", zh));
|
||||
b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en));
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; });
|
||||
b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} });
|
||||
grid.append(b);
|
||||
}
|
||||
box.append(grid);
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} });
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); });
|
||||
@@ -2098,10 +2098,10 @@ function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
* ============================================================ */
|
||||
function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试)
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
`词条:${nameText}\n`
|
||||
+ "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发)),"
|
||||
+ "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。"));
|
||||
@@ -2113,9 +2113,9 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存");
|
||||
box.append(tip);
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
row.append(clrBtn, cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -2139,11 +2139,11 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
/** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */
|
||||
function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "slotname"; // 稳定锤点
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
const mk = (label, val, ph) => {
|
||||
box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label));
|
||||
box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label));
|
||||
const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;");
|
||||
i.type = "text";
|
||||
i.value = val || "";
|
||||
@@ -2154,8 +2154,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发");
|
||||
const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair");
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
row.append(cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -2190,12 +2190,12 @@ function renderCategoryPanel(box, api, catId) {
|
||||
catRow.dataset.ippCatRow = "1";
|
||||
for (const c of CATEGORIES) {
|
||||
const on = c.id === catId;
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
b.dataset.cat = c.id;
|
||||
if (on) b.dataset.catActive = "1";
|
||||
b.title = `切到「${c.label}」面板 —— ${c.tip}`;
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (!on && api.switchCategory) api.switchCategory(c.id);
|
||||
@@ -2205,7 +2205,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
box.append(catRow);
|
||||
const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;");
|
||||
bar.append(
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => {
|
||||
s.elements.selected = {};
|
||||
s.elements.rel = {};
|
||||
@@ -2215,17 +2215,17 @@ function renderCategoryPanel(box, api, catId) {
|
||||
notify("已清空提示词", "success");
|
||||
});
|
||||
}),
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
const t = rebuildBody(s, index, lang());
|
||||
bodySet(t);
|
||||
fire();
|
||||
notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning");
|
||||
}),
|
||||
smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(选中有预设的档位时含预设句)", () => {
|
||||
smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(选中有预设的档位时含预设句)", () => {
|
||||
copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io));
|
||||
}),
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
if (!obj || typeof obj !== "object") return;
|
||||
for (const k of ["custom", "noted", "selected"]) {
|
||||
if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]);
|
||||
@@ -2257,7 +2257,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
// 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态)
|
||||
if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" };
|
||||
const ui = CAT_UI[cat.id];
|
||||
const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;");
|
||||
const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;");
|
||||
const selOf = () => (s.elements.selected[cat.id] || []);
|
||||
const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = []));
|
||||
const rebuild = () => { wrap.innerHTML = ""; build(); };
|
||||
@@ -2332,14 +2332,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const cur = subOf(cat, ui.sub).id;
|
||||
for (const c of chipRefs) {
|
||||
const on = c.dataset.sub === cur;
|
||||
c.style.background = on ? "#2d5a88" : "#2a2a2a";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "#555";
|
||||
c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)";
|
||||
c.style.color = on ? "#eaf4ff" : "#bbb";
|
||||
c.style.fontWeight = on ? "600" : "400";
|
||||
}
|
||||
};
|
||||
for (const sub of (cat.subs || [])) {
|
||||
const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
b.dataset.sub = sub.id;
|
||||
b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`;
|
||||
b.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
@@ -2389,7 +2389,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
fire();
|
||||
notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning");
|
||||
};
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
const list = visibleEntries();
|
||||
if (!list.length) { notify("没有可加入的词条", "warning"); return; }
|
||||
if (list.length > 60) {
|
||||
@@ -2398,7 +2398,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}
|
||||
addAllNow(list);
|
||||
}));
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => {
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => {
|
||||
const keys = selOf().slice();
|
||||
if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; }
|
||||
removeKeys(keys);
|
||||
@@ -2428,7 +2428,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(entry.key);
|
||||
const descKey = `${cat.id}|${entry.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
// 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip)
|
||||
row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}`
|
||||
+ (entry.custom ? "\n(自建槽位)" : "")
|
||||
@@ -2447,14 +2447,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderRows();
|
||||
fire();
|
||||
});
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
const rel = relPair(s, cat.id, entry.key);
|
||||
if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh));
|
||||
const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`);
|
||||
row.append(nm);
|
||||
if (cat.id === "props") {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(),
|
||||
(relPair(s, cat.id, entry.key) || {}).id || "",
|
||||
(relId) => {
|
||||
@@ -2466,7 +2466,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}));
|
||||
}
|
||||
const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2505,7 +2505,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(it.key);
|
||||
const descKey = `${cat.id}|${it.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)`
|
||||
+ `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}`
|
||||
+ "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)");
|
||||
@@ -2515,10 +2515,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
});
|
||||
row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`));
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
openSlotNameEditor(it.zh, it.en, (z, e2) => {
|
||||
it.zh = z; it.en = e2;
|
||||
if (entry) { entry.zh = z; entry.en = e2; }
|
||||
@@ -2528,7 +2528,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
});
|
||||
}));
|
||||
row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2543,7 +2543,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
api.refreshPreview(null);
|
||||
fire();
|
||||
})));
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => {
|
||||
removeKeys([it.key]);
|
||||
delete s.elements.noted[`${cat.id}|${it.key}`];
|
||||
@@ -2561,7 +2561,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderCustom();
|
||||
|
||||
const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;");
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []);
|
||||
arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" });
|
||||
index = buildIndex(s.elements);
|
||||
@@ -2569,7 +2569,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
}));
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
const arr = s.elements.custom[cat.id] || [];
|
||||
if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; }
|
||||
const last = arr[arr.length - 1];
|
||||
@@ -2592,10 +2592,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
/** 分类外壳:标题栏总是建,内容懒建(展开才建) */
|
||||
function build() {
|
||||
wrap.innerHTML = "";
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:#8a97a5;", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt,
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt,
|
||||
el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || ""));
|
||||
const body = el("div", "padding:8px 10px;");
|
||||
body.style.display = ui.collapsed ? "none" : "block";
|
||||
@@ -2810,11 +2810,11 @@ function setupNode(node) {
|
||||
const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const b of PANEL_BUTTONS) {
|
||||
const cat = catOf(b.panel);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`;
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const settings = loadSettings(node);
|
||||
@@ -2871,17 +2871,17 @@ function setupNode(node) {
|
||||
btn.title = ref && ref.path
|
||||
? `已选:${ref.name || ref.path}\n${ref.path}\n点击可重新选择`
|
||||
: `尚未选择${kind === "pose" ? "姿态" : "穿搭"}参考图,点击打开图库${kind === "pose" ? "(输出端口:姿态图)" : "(输出端口:穿搭图)"}`;
|
||||
btn.style.color = ref && ref.path ? "#d8ffd8" : "#eee";
|
||||
btn.style.color = ref && ref.path ? "#d8ffd8" : "var(--xb-text)";
|
||||
};
|
||||
if (isGalleryNode(node)) {
|
||||
const refRow = el("div", "display:grid;grid-template-columns:repeat(2,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const kind of ["pose", "costume"]) {
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;"
|
||||
+ "border-radius:6px;border:2px solid #c58a3a;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;"
|
||||
+ "border-radius:6px;border:2px solid #c58a3a;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;"
|
||||
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", REF_LABELS[kind] + "选择");
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const widgetRef = findWidget(node, REF_FIELDS[kind]);
|
||||
@@ -2915,7 +2915,7 @@ function setupNode(node) {
|
||||
|
||||
// ③ 提示词框(flex:1 1 auto 吃掉剩余高度;原来的「生成详情」状态行已按要求删除)
|
||||
// 右上角三个小按钮:添加预设 / 编辑预设 / 复制文本(都作用于这一个提示词文本框)
|
||||
const promptLab = el("div", "font-size:12px;color:#bbb;flex:1 1 auto;min-width:0;",
|
||||
const promptLab = el("div", "font-size:12px;color:var(--xb-text-dim);flex:1 1 auto;min-width:0;",
|
||||
"📝 节点提示词(与面板预览框双向同步)");
|
||||
const promptBtns = el("div", "display:flex;gap:6px;align-items:center;flex:0 0 auto;");
|
||||
promptBtns.append(
|
||||
@@ -2972,11 +2972,11 @@ function setupNode(node) {
|
||||
b.textContent = label;
|
||||
b.title = tip;
|
||||
b.style.cssText = "font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;"
|
||||
+ "border:1px solid #555;background:#2b2b2b;color:#ddd;cursor:pointer;white-space:nowrap;";
|
||||
+ "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);cursor:pointer;white-space:nowrap;";
|
||||
b.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||
b.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); onClick?.(); });
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "#2b2b2b"; });
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-bg-raise)"; });
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -3040,22 +3040,22 @@ function setupNode(node) {
|
||||
card.style.cssText = "width:" + (opts.width || MODAL_DEFAULT_WIDTH) + "px;max-width:96vw;max-height:90vh;"
|
||||
+ "margin:auto;flex:0 0 auto;"
|
||||
+ "display:flex;flex-direction:column;"
|
||||
+ "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;"
|
||||
+ "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);"
|
||||
+ "font:15px/1.65 system-ui,sans-serif;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;";
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.style.cssText = "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;"
|
||||
head.style.cssText = "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);"
|
||||
+ "display:flex;align-items:center;gap:12px;flex-wrap:wrap;";
|
||||
const titleEl = document.createElement("span");
|
||||
titleEl.textContent = title;
|
||||
titleEl.style.cssText = "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:#eee;";
|
||||
titleEl.style.cssText = "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:var(--xb-text);";
|
||||
head.append(titleEl);
|
||||
|
||||
const tools = document.createElement("span");
|
||||
tools.style.cssText = "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;";
|
||||
// 字号
|
||||
const fontVal = document.createElement("span");
|
||||
fontVal.style.cssText = "min-width:26px;text-align:center;color:#e8a87c;font-variant-numeric:tabular-nums;";
|
||||
fontVal.style.cssText = "min-width:26px;text-align:center;color:var(--xb-orange);font-variant-numeric:tabular-nums;";
|
||||
fontVal.textContent = String(modalFontGet());
|
||||
const fontMinus = mkTinyBtn("A−", "缩小弹窗字号(Ctrl/⌘+滚轮 也可)", () => {
|
||||
modalFontSet(modalFontGet() - 1); fontVal.textContent = String(modalFontGet()); applyModalScale(card);
|
||||
@@ -3065,7 +3065,7 @@ function setupNode(node) {
|
||||
});
|
||||
// 界面缩放
|
||||
const zoomVal = document.createElement("span");
|
||||
zoomVal.style.cssText = "min-width:44px;text-align:center;color:#ffd98a;font-variant-numeric:tabular-nums;";
|
||||
zoomVal.style.cssText = "min-width:44px;text-align:center;color:var(--xb-warn);font-variant-numeric:tabular-nums;";
|
||||
zoomVal.textContent = Math.round(modalZoomGet() * 100) + "%";
|
||||
const zoomMinus = mkTinyBtn("−", "缩小整个弹窗界面", () => {
|
||||
modalZoomSet(modalZoomGet() - 0.1); zoomVal.textContent = Math.round(modalZoomGet() * 100) + "%"; applyModalScale(card);
|
||||
@@ -3075,9 +3075,9 @@ function setupNode(node) {
|
||||
});
|
||||
const closeBtn = mkTinyBtn("✕", "关闭", () => close());
|
||||
tools.append(
|
||||
el("span", "color:#8a8a8a;", "字号"), fontMinus, fontVal, fontPlus,
|
||||
el("span", "color:var(--xb-text-dim);", "字号"), fontMinus, fontVal, fontPlus,
|
||||
el("span", "color:#5a5a5a;", "|"),
|
||||
el("span", "color:#8a8a8a;", "界面"), zoomMinus, zoomVal, zoomPlus,
|
||||
el("span", "color:var(--xb-text-dim);", "界面"), zoomMinus, zoomVal, zoomPlus,
|
||||
closeBtn,
|
||||
);
|
||||
head.append(tools);
|
||||
@@ -3154,7 +3154,7 @@ function setupNode(node) {
|
||||
const { bodyEl, close } = openModal("📥 添加预设(点一项 → 加进下面的提示词文本框)");
|
||||
if (!modes.length) {
|
||||
const tip = document.createElement("div");
|
||||
tip.style.cssText = "color:#ffb74d;";
|
||||
tip.style.cssText = "color:var(--xb-warn);";
|
||||
tip.textContent = `没有找到预设文件:xb_skill/${PRESET_NODE_NAME}/${data?.lang || "ZH"} 目录里没有 .txt。`
|
||||
+ "可以先在「编辑预设」里把当前提示词存成一个新预设。";
|
||||
bodyEl.append(tip);
|
||||
@@ -3171,23 +3171,23 @@ function setupNode(node) {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.style.cssText = "text-align:left;padding:7px 10px;border-radius:5px;border:1px solid #3d3d3d;"
|
||||
+ "background:#1f1f1f;color:#ddd;cursor:pointer;font-size:1em;";
|
||||
+ "background:var(--xb-inset);color:var(--xb-text);cursor:pointer;font-size:1em;";
|
||||
const nameSpan = document.createElement("div");
|
||||
nameSpan.textContent = (backupOnly.includes(label) ? "📦 " : "") + label;
|
||||
nameSpan.style.cssText = "font-weight:bold;color:#8fd3ff;font-size:1.05em;";
|
||||
nameSpan.style.cssText = "font-weight:bold;color:var(--xb-info);font-size:1.05em;";
|
||||
nameSpan.title = backupOnly.includes(label) ? "来自备份目录(output/生图提示词预设)" : "来自预设目录(xb_skill)";
|
||||
const prev = document.createElement("div");
|
||||
prev.textContent = txt.length > 60 ? txt.slice(0, 60) + "…" : (txt || "(空文件)");
|
||||
prev.style.cssText = "color:#9a9a9a;font-size:0.9em;margin-top:3px;line-height:1.5;";
|
||||
row.append(nameSpan, prev);
|
||||
row.addEventListener("mouseenter", () => { row.style.background = "#2c3a44"; row.style.borderColor = "#4a7a99"; });
|
||||
row.addEventListener("mouseenter", () => { row.style.background = "var(--xb-bg-raise)"; row.style.borderColor = "#4a7a99"; });
|
||||
row.addEventListener("mouseleave", () => { row.style.background = "#1f1f1f"; row.style.borderColor = "#3d3d3d"; });
|
||||
row.addEventListener("click", () => { applyPresetLabel(label, txt); close(); });
|
||||
list.append(row);
|
||||
}
|
||||
bodyEl.append(list);
|
||||
const bdir = document.createElement("div");
|
||||
bdir.style.cssText = "color:#8a8a8a;font-size:0.88em;margin-top:6px;word-break:break-all;flex:0 0 auto;";
|
||||
bdir.style.cssText = "color:var(--xb-text-dim);font-size:0.88em;margin-top:6px;word-break:break-all;flex:0 0 auto;";
|
||||
bdir.textContent = `📦 备份目录(也会被读取):${data?.backup_dir || "output/生图提示词预设"}`;
|
||||
const foot = document.createElement("div");
|
||||
foot.style.cssText = "display:flex;justify-content:flex-end;gap:6px;margin-top:4px;flex:0 0 auto;flex-wrap:wrap;";
|
||||
@@ -3206,18 +3206,18 @@ function setupNode(node) {
|
||||
const rowSel = document.createElement("div");
|
||||
rowSel.style.cssText = "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;";
|
||||
const sel = document.createElement("select");
|
||||
sel.style.cssText = "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;";
|
||||
rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有预设(选中即载入)"), sel);
|
||||
sel.style.cssText = "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;";
|
||||
rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有预设(选中即载入)"), sel);
|
||||
|
||||
const rowName = document.createElement("div");
|
||||
rowName.style.cssText = "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;";
|
||||
rowName.append(el("span", "color:#aaa;white-space:nowrap;", "预设名称"));
|
||||
rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "预设名称"));
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.type = "text";
|
||||
nameInput.placeholder = "例如:电影感特写(同名 = 覆盖,新名 = 新建)";
|
||||
nameInput.style.cssText = "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;";
|
||||
nameInput.style.cssText = "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;";
|
||||
nameInput.value = lastPresetLabel || "";
|
||||
rowName.append(nameInput);
|
||||
|
||||
@@ -3253,8 +3253,8 @@ function setupNode(node) {
|
||||
// ── 第 2 行:大文本编辑框 ──
|
||||
const boxHead = document.createElement("div");
|
||||
boxHead.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;";
|
||||
const boxLabel = el("span", "color:#9fc3e8;font-weight:bold;", "预设内容(这里是最终会加到提示词最顶端/被保存的正文)");
|
||||
const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字");
|
||||
const boxLabel = el("span", "color:var(--xb-info);font-weight:bold;", "预设内容(这里是最终会加到提示词最顶端/被保存的正文)");
|
||||
const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字");
|
||||
const boxHeadRight = document.createElement("div");
|
||||
boxHeadRight.style.cssText = "display:flex;gap:8px;align-items:center;flex:0 0 auto;";
|
||||
boxHeadRight.append(charCount, fromPromptBtn);
|
||||
@@ -3265,7 +3265,7 @@ function setupNode(node) {
|
||||
box.dataset.autofit = "1";
|
||||
box.placeholder = "在这里编写预设内容…(在左上方选择已有预设会自动载入到这里,或点「取当前提示词」)";
|
||||
box.style.cssText = "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;"
|
||||
+ "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;"
|
||||
+ "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;"
|
||||
+ "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;"
|
||||
+ "font-family:inherit;font-size:" + modalFontGet() + "px;";
|
||||
const fitBox = () => {
|
||||
@@ -3280,11 +3280,11 @@ function setupNode(node) {
|
||||
|
||||
// ── 说明 / 状态 ──
|
||||
const hint = document.createElement("div");
|
||||
hint.style.cssText = "color:#8a8a8a;font-size:0.88em;word-break:break-all;";
|
||||
hint.style.cssText = "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;";
|
||||
hint.textContent = `保存位置:xb_skill/${PRESET_NODE_NAME}/${data?.lang || "ZH"}/ 备份:${data?.backup_dir || "output/生图提示词预设"}`;
|
||||
|
||||
const status = document.createElement("div");
|
||||
status.style.cssText = "min-height:18px;color:#aaa;";
|
||||
status.style.cssText = "min-height:18px;color:var(--xb-text-dim);";
|
||||
|
||||
// ── 底部按钮 ──
|
||||
const foot = document.createElement("div");
|
||||
|
||||
@@ -1303,15 +1303,15 @@ function el(tag, css, text) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
|
||||
function makeSectionTitle(text) {
|
||||
return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text);
|
||||
return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text);
|
||||
}
|
||||
|
||||
function field(labelText, control) {
|
||||
const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;");
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
control.style.flex = "1 1 0";
|
||||
control.style.minWidth = "0";
|
||||
g.append(lab, control);
|
||||
@@ -1338,7 +1338,7 @@ function radioRow(options, value, onChange) {
|
||||
for (const o of options) {
|
||||
const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o);
|
||||
const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o);
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = String(value) === val;
|
||||
@@ -1365,7 +1365,7 @@ function textareaControl(value, onChange, css) {
|
||||
}
|
||||
|
||||
function checkboxControl(value, labelText, onChange, tip) {
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;");
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;");
|
||||
const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = !!value;
|
||||
@@ -1427,10 +1427,10 @@ function zoomSync(root) {
|
||||
}
|
||||
function buildZoomRow(root) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
val.classList.add("ipp-zoom-val");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
zoomSet((zoomGet() == null ? 12 : zoomGet()) + d);
|
||||
zoomApplyContainer(root);
|
||||
@@ -1474,10 +1474,10 @@ window.addEventListener("resize", () => {
|
||||
function buildUIRow(target) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面"));
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
val.classList.add("ipp-ui-val");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
uiSet(uiGet() + d * UI_STEP);
|
||||
uiApply(target);
|
||||
@@ -1741,12 +1741,12 @@ function openPanel(ctx, panelId) {
|
||||
const title = `${cat.label} 元素`;
|
||||
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;");
|
||||
const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;");
|
||||
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title);
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title);
|
||||
header.append(titleEl);
|
||||
header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
dialog.append(header);
|
||||
|
||||
// ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里
|
||||
@@ -1754,12 +1754,12 @@ function openPanel(ctx, panelId) {
|
||||
const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;");
|
||||
|
||||
// ── 吸顶预览框(不受滚动条约束,始终在画面内) ──
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;");
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);");
|
||||
const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;");
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
const headCount = el("span", "font-size:11px;color:#888;", "");
|
||||
head.append(headCount);
|
||||
const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;");
|
||||
preview.spellcheck = false;
|
||||
preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…";
|
||||
@@ -1800,10 +1800,10 @@ function openPanel(ctx, panelId) {
|
||||
|
||||
// ── 底部 ──
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
autoCb.type = "checkbox";
|
||||
autoCb.checked = settings.auto_save !== false;
|
||||
@@ -1888,13 +1888,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不
|
||||
* ============================================================ */
|
||||
function openConfirm(title, message, onOk, okText) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "confirm";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} });
|
||||
@@ -1912,29 +1912,29 @@ function openConfirm(title, message, onOk, okText) {
|
||||
* ============================================================ */
|
||||
function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "rel";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;",
|
||||
`道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`));
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
for (const [id, zh, en] of RELATIONS) {
|
||||
const on = id === current;
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`);
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`);
|
||||
b.dataset.rel = id;
|
||||
b.title = `${zh} / ${en}`;
|
||||
b.append(el("span", "font-size:13px;line-height:1.2;", zh));
|
||||
b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en));
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; });
|
||||
b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} });
|
||||
grid.append(b);
|
||||
}
|
||||
box.append(grid);
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} });
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); });
|
||||
@@ -1952,10 +1952,10 @@ function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
* ============================================================ */
|
||||
function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试)
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
`词条:${nameText}\n`
|
||||
+ "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发)),"
|
||||
+ "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。"));
|
||||
@@ -1967,9 +1967,9 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存");
|
||||
box.append(tip);
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
row.append(clrBtn, cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -1993,11 +1993,11 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
/** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */
|
||||
function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "slotname"; // 稳定锤点
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
const mk = (label, val, ph) => {
|
||||
box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label));
|
||||
box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label));
|
||||
const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;");
|
||||
i.type = "text";
|
||||
i.value = val || "";
|
||||
@@ -2008,8 +2008,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发");
|
||||
const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair");
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
row.append(cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -2044,12 +2044,12 @@ function renderCategoryPanel(box, api, catId) {
|
||||
catRow.dataset.ippCatRow = "1";
|
||||
for (const c of CATEGORIES) {
|
||||
const on = c.id === catId;
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
b.dataset.cat = c.id;
|
||||
if (on) b.dataset.catActive = "1";
|
||||
b.title = `切到「${c.label}」面板 —— ${c.tip}`;
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (!on && api.switchCategory) api.switchCategory(c.id);
|
||||
@@ -2059,7 +2059,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
box.append(catRow);
|
||||
const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;");
|
||||
bar.append(
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => {
|
||||
s.elements.selected = {};
|
||||
s.elements.rel = {};
|
||||
@@ -2069,17 +2069,17 @@ function renderCategoryPanel(box, api, catId) {
|
||||
notify("已清空提示词", "success");
|
||||
});
|
||||
}),
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
const t = rebuildBody(s, index, lang());
|
||||
bodySet(t);
|
||||
fire();
|
||||
notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning");
|
||||
}),
|
||||
smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => {
|
||||
smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => {
|
||||
copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io));
|
||||
}),
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
if (!obj || typeof obj !== "object") return;
|
||||
for (const k of ["custom", "noted", "selected"]) {
|
||||
if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]);
|
||||
@@ -2111,7 +2111,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
// 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态)
|
||||
if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" };
|
||||
const ui = CAT_UI[cat.id];
|
||||
const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;");
|
||||
const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;");
|
||||
const selOf = () => (s.elements.selected[cat.id] || []);
|
||||
const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = []));
|
||||
const rebuild = () => { wrap.innerHTML = ""; build(); };
|
||||
@@ -2186,14 +2186,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const cur = subOf(cat, ui.sub).id;
|
||||
for (const c of chipRefs) {
|
||||
const on = c.dataset.sub === cur;
|
||||
c.style.background = on ? "#2d5a88" : "#2a2a2a";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "#555";
|
||||
c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)";
|
||||
c.style.color = on ? "#eaf4ff" : "#bbb";
|
||||
c.style.fontWeight = on ? "600" : "400";
|
||||
}
|
||||
};
|
||||
for (const sub of (cat.subs || [])) {
|
||||
const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
b.dataset.sub = sub.id;
|
||||
b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`;
|
||||
b.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
@@ -2243,7 +2243,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
fire();
|
||||
notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning");
|
||||
};
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
const list = visibleEntries();
|
||||
if (!list.length) { notify("没有可加入的词条", "warning"); return; }
|
||||
if (list.length > 60) {
|
||||
@@ -2252,7 +2252,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}
|
||||
addAllNow(list);
|
||||
}));
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => {
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => {
|
||||
const keys = selOf().slice();
|
||||
if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; }
|
||||
removeKeys(keys);
|
||||
@@ -2282,7 +2282,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(entry.key);
|
||||
const descKey = `${cat.id}|${entry.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
// 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip)
|
||||
row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}`
|
||||
+ (entry.custom ? "\n(自建槽位)" : "")
|
||||
@@ -2301,14 +2301,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderRows();
|
||||
fire();
|
||||
});
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
const rel = relPair(s, cat.id, entry.key);
|
||||
if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh));
|
||||
const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`);
|
||||
row.append(nm);
|
||||
if (cat.id === "props") {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(),
|
||||
(relPair(s, cat.id, entry.key) || {}).id || "",
|
||||
(relId) => {
|
||||
@@ -2320,7 +2320,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}));
|
||||
}
|
||||
const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2359,7 +2359,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(it.key);
|
||||
const descKey = `${cat.id}|${it.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)`
|
||||
+ `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}`
|
||||
+ "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)");
|
||||
@@ -2369,10 +2369,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
});
|
||||
row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`));
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
openSlotNameEditor(it.zh, it.en, (z, e2) => {
|
||||
it.zh = z; it.en = e2;
|
||||
if (entry) { entry.zh = z; entry.en = e2; }
|
||||
@@ -2382,7 +2382,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
});
|
||||
}));
|
||||
row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2397,7 +2397,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
api.refreshPreview(null);
|
||||
fire();
|
||||
})));
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => {
|
||||
removeKeys([it.key]);
|
||||
delete s.elements.noted[`${cat.id}|${it.key}`];
|
||||
@@ -2415,7 +2415,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderCustom();
|
||||
|
||||
const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;");
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []);
|
||||
arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" });
|
||||
index = buildIndex(s.elements);
|
||||
@@ -2423,7 +2423,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
}));
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
const arr = s.elements.custom[cat.id] || [];
|
||||
if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; }
|
||||
const last = arr[arr.length - 1];
|
||||
@@ -2446,10 +2446,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
/** 分类外壳:标题栏总是建,内容懒建(展开才建) */
|
||||
function build() {
|
||||
wrap.innerHTML = "";
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:#8a97a5;", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt,
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt,
|
||||
el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || ""));
|
||||
const body = el("div", "padding:8px 10px;");
|
||||
body.style.display = ui.collapsed ? "none" : "block";
|
||||
@@ -2673,11 +2673,11 @@ function setupNode(node) {
|
||||
const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const b of PANEL_BUTTONS) {
|
||||
const cat = catOf(b.panel);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`;
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const settings = loadSettings(node);
|
||||
@@ -2776,9 +2776,9 @@ function setupNode(node) {
|
||||
box.style.margin = "0 8px";
|
||||
box.style.boxSizing = "border-box";
|
||||
box.style.borderRadius = "4px";
|
||||
box.style.border = "1px solid #444";
|
||||
box.style.background = "#1d1d1d";
|
||||
box.style.color = "#ccc";
|
||||
box.style.border = "1px solid var(--xb-border)";
|
||||
box.style.background = "var(--xb-bg)";
|
||||
box.style.color = "var(--xb-text)";
|
||||
box.style.padding = "5px 8px";
|
||||
box.style.fontSize = "12px";
|
||||
box.style.lineHeight = "1.6";
|
||||
@@ -3463,7 +3463,7 @@ function xbrTextControl(value, placeholder, onChange) {
|
||||
/** 导演台同款「种子 + 生成后控制(随机/固定/增加/减少)」一行 */
|
||||
function xbrSeedRow(run, onChange) {
|
||||
const g = el("div", "display:flex;align-items:center;gap:10px;margin-bottom:10px;");
|
||||
g.append(el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子"));
|
||||
g.append(el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子"));
|
||||
const i = xbrNumberControl(run.seed, { min: 0, max: Number.MAX_SAFE_INTEGER, step: 1 }, (v) => { run.seed = Math.round(v); onChange?.(); });
|
||||
i.style.flex = "1 1 0";
|
||||
const ctrl = smallBtn("", "flex:1 1 0;min-width:0;background:#b45309;border:1px solid #d97706;color:#fff;text-align:center;", "生成后控制:点击在 随机 / 固定 / 增加 / 减少 间切换", null);
|
||||
@@ -3486,24 +3486,24 @@ function xbrSeedRow(run, onChange) {
|
||||
let xbrModal = null;
|
||||
function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, onCommit = null, onImmediate = null, onChange = null, onClose = null }) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
"max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;");
|
||||
const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
"max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;");
|
||||
dialog.dataset.xbrProModal = label;
|
||||
dialog.setAttribute("role", "dialog");
|
||||
dialog.setAttribute("aria-modal", "true");
|
||||
dialog.setAttribute("aria-label", label);
|
||||
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid #444;");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:#eee;", title));
|
||||
if (subtitle) header.append(el("div", "font-size:12px;color:#999;margin-top:5px;line-height:1.6;", subtitle));
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--xb-border);");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:var(--xb-text);", title));
|
||||
if (subtitle) header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;line-height:1.6;", subtitle));
|
||||
const body = el("div", "padding:16px 20px;overflow-y:auto;flex:1;min-height:200px;");
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;");
|
||||
|
||||
let autoCb = null;
|
||||
if (autoSaveRef) {
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
autoCb.type = "checkbox";
|
||||
autoCb.checked = autoSaveRef.value !== false;
|
||||
@@ -3518,8 +3518,8 @@ function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, on
|
||||
uiRow.title = "调整整个弹窗界面大小(不缩放网页/画布;Ctrl/⌘+滚轮 也可)";
|
||||
left.append(zoomRow, uiRow);
|
||||
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = onCommit ? el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null;
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = onCommit ? el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null;
|
||||
footer.append(left, cancelBtn);
|
||||
if (saveBtn) footer.append(saveBtn);
|
||||
dialog.append(header, body, error, footer);
|
||||
@@ -3766,15 +3766,15 @@ function xbrCopyPromptText(node) {
|
||||
function xbrPresetDialog(title) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;overflow:auto;");
|
||||
const dialog = el("section", "width:880px;max-width:96vw;max-height:90vh;margin:auto;flex:0 0 auto;"
|
||||
+ "display:flex;flex-direction:column;background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;"
|
||||
+ "display:flex;flex-direction:column;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);"
|
||||
+ "font:15px/1.65 system-ui,sans-serif;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;");
|
||||
const header = el("div", "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;"
|
||||
const header = el("div", "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);"
|
||||
+ "display:flex;align-items:center;gap:12px;flex-wrap:wrap;");
|
||||
header.append(el("span", "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:#eee;", title));
|
||||
header.append(el("span", "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:var(--xb-text);", title));
|
||||
const tools = el("span", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;");
|
||||
tools.append(el("span", "color:#8a8a8a;", "字号"), buildZoomRow(dialog),
|
||||
tools.append(el("span", "color:var(--xb-text-dim);", "字号"), buildZoomRow(dialog),
|
||||
el("span", "color:#5a5a5a;", "|"), buildUIRow(dialog));
|
||||
const closeBtn = smallBtn("✕", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:3px 10px;", "关闭");
|
||||
const closeBtn = smallBtn("✕", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:3px 10px;", "关闭");
|
||||
tools.append(closeBtn);
|
||||
header.append(tools);
|
||||
const body = el("div", "padding:14px 18px 16px;display:flex;flex-direction:column;gap:10px;overflow:hidden;flex:1 1 auto;min-height:0;");
|
||||
@@ -3802,7 +3802,7 @@ async function xbrShowPresetPicker(node, onPick) {
|
||||
const backupOnly = Array.isArray(data?.backup_only) ? data.backup_only : [];
|
||||
const { body, close } = xbrPresetDialog("📥 添加预设(点一项 → 加进节点提示词)");
|
||||
if (!labels.length) {
|
||||
body.append(el("div", "color:#ffb74d;",
|
||||
body.append(el("div", "color:var(--xb-warn);",
|
||||
`没有找到预设文件:xb_skill/${XBR_PRESET_LIB_NODE}/${data?.lang || "ZH"} 目录里没有 .txt。`
|
||||
+ "可以先在「✏️ 编辑预设」里把当前提示词存成一个新预设。"));
|
||||
}
|
||||
@@ -3810,14 +3810,14 @@ async function xbrShowPresetPicker(node, onPick) {
|
||||
for (const label of labels) {
|
||||
const txt = xbrLibText(label, lang);
|
||||
const row = el("button", "text-align:left;padding:7px 10px;border-radius:5px;border:1px solid #3d3d3d;"
|
||||
+ "background:#1f1f1f;color:#ddd;cursor:pointer;font-size:1em;font-family:inherit;");
|
||||
+ "background:var(--xb-inset);color:var(--xb-text);cursor:pointer;font-size:1em;font-family:inherit;");
|
||||
row.type = "button";
|
||||
const name = el("div", "font-weight:bold;color:#8fd3ff;font-size:1.05em;", (backupOnly.includes(label) ? "📦 " : "") + label);
|
||||
const name = el("div", "font-weight:bold;color:var(--xb-info);font-size:1.05em;", (backupOnly.includes(label) ? "📦 " : "") + label);
|
||||
name.title = backupOnly.includes(label) ? "来自备份目录(output/生图提示词预设)" : "来自预设目录(xb_skill)";
|
||||
const prev = el("div", "color:#9a9a9a;font-size:0.9em;margin-top:3px;line-height:1.5;",
|
||||
txt.length > 60 ? txt.slice(0, 60) + "…" : (txt || "(空文件)"));
|
||||
row.append(name, prev);
|
||||
row.addEventListener("mouseenter", () => { row.style.background = "#2c3a44"; row.style.borderColor = "#4a7a99"; });
|
||||
row.addEventListener("mouseenter", () => { row.style.background = "var(--xb-bg-raise)"; row.style.borderColor = "#4a7a99"; });
|
||||
row.addEventListener("mouseleave", () => { row.style.background = "#1f1f1f"; row.style.borderColor = "#3d3d3d"; });
|
||||
row.addEventListener("click", () => {
|
||||
if (typeof onPick === "function") onPick(label, txt);
|
||||
@@ -3827,13 +3827,13 @@ async function xbrShowPresetPicker(node, onPick) {
|
||||
list.append(row);
|
||||
}
|
||||
body.append(list);
|
||||
body.append(el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
body.append(el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
`📦 备份目录(也会被读取):${data?.backup_dir || "output/生图提示词预设"}`));
|
||||
const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;");
|
||||
foot.append(
|
||||
smallBtn("刷新列表", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 12px;font-size:12px;", "重新扫描预设目录与备份目录",
|
||||
smallBtn("刷新列表", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 12px;font-size:12px;", "重新扫描预设目录与备份目录",
|
||||
async () => { close(); await xbrFetchPresetLib(true); xbrShowPresetPicker(node); }),
|
||||
smallBtn("关闭", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 12px;font-size:12px;", "关闭窗口", close),
|
||||
smallBtn("关闭", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 12px;font-size:12px;", "关闭窗口", close),
|
||||
);
|
||||
body.append(foot);
|
||||
}
|
||||
@@ -3869,13 +3869,13 @@ async function xbrShowPresetEditor(node, opts = {}) {
|
||||
const { body, close } = xbrPresetDialog(cfg.title);
|
||||
|
||||
const rowSel = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;");
|
||||
const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;");
|
||||
rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有预设(选中即载入)"), sel);
|
||||
const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;");
|
||||
rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有预设(选中即载入)"), sel);
|
||||
const rowName = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;");
|
||||
rowName.append(el("span", "color:#aaa;white-space:nowrap;", "预设名称"));
|
||||
const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;");
|
||||
rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "预设名称"));
|
||||
const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;");
|
||||
nameInput.type = "text";
|
||||
nameInput.placeholder = "例如:电影感特写(同名 = 覆盖,新名 = 新建)";
|
||||
rowName.append(nameInput);
|
||||
@@ -3884,10 +3884,10 @@ async function xbrShowPresetEditor(node, opts = {}) {
|
||||
|
||||
const boxHead = el("div", "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;flex:0 0 auto;");
|
||||
const boxHeadRight = el("div", "display:flex;gap:8px;align-items:center;flex:0 0 auto;");
|
||||
const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字");
|
||||
const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字");
|
||||
boxHeadRight.append(charCount);
|
||||
if (cfg.fromPrompt) {
|
||||
boxHeadRight.append(smallBtn("取当前提示词", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:4px 10px;font-size:12px;",
|
||||
boxHeadRight.append(smallBtn("取当前提示词", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;font-size:12px;",
|
||||
"把节点提示词框的内容复制到编辑框", () => {
|
||||
box.value = xbrPromptText(node);
|
||||
status.style.color = "#aaa";
|
||||
@@ -3895,10 +3895,10 @@ async function xbrShowPresetEditor(node, opts = {}) {
|
||||
fitBox();
|
||||
}));
|
||||
}
|
||||
boxHead.append(el("span", "color:#9fc3e8;font-weight:bold;", cfg.contentLabel), boxHeadRight);
|
||||
boxHead.append(el("span", "color:var(--xb-info);font-weight:bold;", cfg.contentLabel), boxHeadRight);
|
||||
|
||||
const box = el("textarea", "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;"
|
||||
+ "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;"
|
||||
+ "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;"
|
||||
+ "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-family:inherit;font-size:15px;");
|
||||
box.spellcheck = false;
|
||||
box.placeholder = cfg.placeholder;
|
||||
@@ -3910,12 +3910,12 @@ async function xbrShowPresetEditor(node, opts = {}) {
|
||||
box.addEventListener("input", fitBox);
|
||||
fitBox();
|
||||
|
||||
const hint = el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
const hint = el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
`保存位置:xb_skill/${cfg.nodeName}/${data?.lang || "ZH"}/ 备份:${data?.backup_dir || "output/生图提示词预设"}`);
|
||||
const status = el("div", "min-height:18px;color:#aaa;flex:0 0 auto;");
|
||||
const status = el("div", "min-height:18px;color:var(--xb-text-dim);flex:0 0 auto;");
|
||||
|
||||
const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;");
|
||||
const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 14px;font-size:12px;";
|
||||
const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 14px;font-size:12px;";
|
||||
|
||||
const fillSelect = (d) => {
|
||||
const cur = sel.value;
|
||||
@@ -3950,7 +3950,7 @@ async function xbrShowPresetEditor(node, opts = {}) {
|
||||
try { nameInput.focus(); } catch (_) {}
|
||||
});
|
||||
|
||||
const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:#eaf4ff;",
|
||||
const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);",
|
||||
"把编辑框内容保存成预设文件(同名覆盖 / 新名新建)", async () => {
|
||||
const label = String(nameInput.value || "").trim();
|
||||
if (!label) { status.style.color = "#ff8a80"; status.textContent = "请先填写预设名称"; nameInput.focus(); return; }
|
||||
@@ -4096,14 +4096,14 @@ async function xbrShowSkillEditor(node, presetName, onChanged) {
|
||||
const { body, close } = xbrPresetDialog("✏️ 编辑 skill(技能文档整段作为 LLM 的系统提示词)");
|
||||
|
||||
const rowSel = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;");
|
||||
const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;");
|
||||
rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有 skill(选中即载入)"), sel);
|
||||
const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;");
|
||||
rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有 skill(选中即载入)"), sel);
|
||||
|
||||
const rowName = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;");
|
||||
rowName.append(el("span", "color:#aaa;white-space:nowrap;", "文件名"));
|
||||
const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;"
|
||||
+ "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;");
|
||||
rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "文件名"));
|
||||
const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);"
|
||||
+ "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;");
|
||||
nameInput.type = "text";
|
||||
nameInput.placeholder = "例如:my_skill.txt(同名 = 覆盖,新名 = 新建)";
|
||||
rowName.append(nameInput);
|
||||
@@ -4111,11 +4111,11 @@ async function xbrShowSkillEditor(node, presetName, onChanged) {
|
||||
firstRow.append(rowSel, rowName);
|
||||
|
||||
const boxHead = el("div", "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;flex:0 0 auto;");
|
||||
const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字");
|
||||
boxHead.append(el("span", "color:#9fc3e8;font-weight:bold;", "skill 全文(整段作为系统提示词)"), charCount);
|
||||
const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字");
|
||||
boxHead.append(el("span", "color:var(--xb-info);font-weight:bold;", "skill 全文(整段作为系统提示词)"), charCount);
|
||||
|
||||
const box = el("textarea", "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;"
|
||||
+ "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;"
|
||||
+ "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;"
|
||||
+ "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-family:inherit;font-size:15px;");
|
||||
box.spellcheck = false;
|
||||
box.placeholder = "在这里编写技能文档(角色设定 + 输出契约)…";
|
||||
@@ -4127,11 +4127,11 @@ async function xbrShowSkillEditor(node, presetName, onChanged) {
|
||||
box.addEventListener("input", fitBox);
|
||||
fitBox();
|
||||
|
||||
const hint = el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
const hint = el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;",
|
||||
"保存位置:xb_skill/skills/ (同一批文件也被「🖼️ Qwen2.1提示词预设」等节点共用)");
|
||||
const status = el("div", "min-height:18px;color:#aaa;flex:0 0 auto;");
|
||||
const status = el("div", "min-height:18px;color:var(--xb-text-dim);flex:0 0 auto;");
|
||||
const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;");
|
||||
const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 14px;font-size:12px;";
|
||||
const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 14px;font-size:12px;";
|
||||
|
||||
const fillSelect = () => {
|
||||
const cur = sel.value;
|
||||
@@ -4166,7 +4166,7 @@ async function xbrShowSkillEditor(node, presetName, onChanged) {
|
||||
try { nameInput.focus(); } catch (_) {}
|
||||
});
|
||||
|
||||
const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:#eaf4ff;",
|
||||
const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);",
|
||||
"保存技能文件(同名覆盖 / 新名新建)", async () => {
|
||||
const name = String(nameInput.value || "").trim();
|
||||
if (!name) { status.style.color = "#ff8a80"; status.textContent = "请先填写文件名"; nameInput.focus(); return; }
|
||||
@@ -4207,7 +4207,7 @@ async function xbrShowSkillEditor(node, presetName, onChanged) {
|
||||
|
||||
/* ── 弹窗内容:✨ 提示词增强预设 ─────────────────────────── */
|
||||
function xbrRenderPreset(body, node, draft, ctx) {
|
||||
const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:4px 12px;font-size:12px;";
|
||||
const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 12px;font-size:12px;";
|
||||
|
||||
// ── 设定词:文本框 + 预设词工具(添加 / 编辑 / 复制)──────────────────────
|
||||
body.append(makeSectionTitle("设定词"));
|
||||
@@ -4315,7 +4315,7 @@ function xbrRenderPreset(body, node, draft, ctx) {
|
||||
if (presetOpts.length) {
|
||||
body.append(field("增强预设", selectControl(presetOpts, draft.preset, (v) => { draft.preset = v; })));
|
||||
} else {
|
||||
body.append(el("div", "font-size:11px;color:#ffb74d;", "没有可用的增强预设(检查 LLM 预设模块是否正常加载)"));
|
||||
body.append(el("div", "font-size:11px;color:var(--xb-warn);", "没有可用的增强预设(检查 LLM 预设模块是否正常加载)"));
|
||||
}
|
||||
body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;",
|
||||
`该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 的系统提示词;只列与输出语言 ${draft.lang} 相符的预设。`));
|
||||
@@ -4350,7 +4350,7 @@ function xbrRenderPreset(body, node, draft, ctx) {
|
||||
if (taskOpts.length) {
|
||||
body.append(field("反推预设", selectControl(taskOpts, draft.task_preset, (v) => { draft.task_preset = v; })));
|
||||
} else {
|
||||
body.append(el("div", "font-size:11px;color:#ffb74d;", "没有可用的反推预设"));
|
||||
body.append(el("div", "font-size:11px;color:var(--xb-warn);", "没有可用的反推预设"));
|
||||
}
|
||||
body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;",
|
||||
"带 * 的预设里 * 是必填占位符,由节点上的提示词框或外接「📝 提示词」填入。"));
|
||||
@@ -4465,7 +4465,7 @@ function xbrRenderHelp(body) {
|
||||
" · 节点表面参数很少?故意的:选项都收在弹窗里,表面只留最常动的几个",
|
||||
" · 改完参数要重新执行节点才生效",
|
||||
];
|
||||
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")));
|
||||
body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n")));
|
||||
}
|
||||
|
||||
const XBR_PANEL_RENDER = { llm: xbrRenderLlm, preset: xbrRenderPreset, help: xbrRenderHelp };
|
||||
@@ -4571,12 +4571,12 @@ async function xbrOpenModal(node, panelId) {
|
||||
function xbrBuildButtonRow(node) {
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(" + Math.max(1, XBR_PANEL_BUTTONS.length) + ",1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const b of XBR_PANEL_BUTTONS) {
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
btn.title = `打开「${b.title}」弹窗`;
|
||||
btn.dataset.xbrProBtn = b.id;
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => { e.stopPropagation(); xbrOpenModal(node, b.id); });
|
||||
grid.append(btn);
|
||||
}
|
||||
@@ -4622,13 +4622,13 @@ function xbrInfoLines(node, promptText) {
|
||||
/** 提示词框标题行(左标签 + 右「复制文本」)+ 参数设定显示;并挂上端口锁定/执行回写/摘要刷新 */
|
||||
function xbrBuildPromptHeader(node, promptBox) {
|
||||
const head = el("div", "display:flex;align-items:center;gap:8px;flex:0 0 auto;");
|
||||
const lab = el("div", "font-size:12px;color:#bbb;", "📝 节点提示词(与面板预览框双向同步)");
|
||||
const lab = el("div", "font-size:12px;color:var(--xb-text-dim);", "📝 节点提示词(与面板预览框双向同步)");
|
||||
// 与「🖼️ 生图提示词预设」的提示词框右上角按钮完全同一套尺寸 / 配色
|
||||
const copy = smallBtn("复制文本", "margin-left:auto;font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;border:1px solid #555;background:#2b2b2b;color:#ddd;white-space:nowrap;",
|
||||
const copy = smallBtn("复制文本", "margin-left:auto;font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);white-space:nowrap;",
|
||||
"复制提示词框内容", () => { copyText(promptBox.value || ""); });
|
||||
head.append(lab, copy);
|
||||
|
||||
const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:#1c1c1e;border:1px solid #333;border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;");
|
||||
const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;");
|
||||
infoBox.dataset.captureWheel = "true";
|
||||
infoBox.title = "当前配置摘要(只读)";
|
||||
|
||||
@@ -4642,8 +4642,8 @@ function xbrBuildPromptHeader(node, promptBox) {
|
||||
const inp = (node.inputs || []).find((i) => i.name === "text");
|
||||
const linked = !!inp && inp.link != null;
|
||||
promptBox.readOnly = linked;
|
||||
promptBox.style.background = linked ? "#141416" : "#1d1d1d";
|
||||
promptBox.style.color = linked ? "#8a8a8a" : "#ccc";
|
||||
promptBox.style.background = linked ? "#141416" : "var(--xb-bg)";
|
||||
promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--xb-text)";
|
||||
promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : "";
|
||||
lab.textContent = linked ? "📝 提示词已锁定(由「📝 提示词」端口控制)" : "📝 节点提示词(与面板预览框双向同步)";
|
||||
lab.style.color = linked ? "#d9a441" : "#bbb";
|
||||
|
||||
@@ -51,10 +51,10 @@ function setupDynamicOutputs(node) {
|
||||
const dw = res.widget;
|
||||
if (dw.inputEl) {
|
||||
dw.inputEl.readOnly = true;
|
||||
dw.inputEl.style.backgroundColor = "#2a2a2a";
|
||||
dw.inputEl.style.backgroundColor = "var(--xb-bg-raise)";
|
||||
dw.inputEl.style.color = "#bbbbbb";
|
||||
dw.inputEl.style.fontSize = "12px";
|
||||
dw.inputEl.style.border = "1px solid #444";
|
||||
dw.inputEl.style.border = "1px solid var(--xb-border)";
|
||||
}
|
||||
// Nodes 2.0:显示型字段走 options.read_only(Vue 部件没有 element 可改)
|
||||
try { if (dw.options) dw.options.read_only = true; } catch (_) {}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* ② 手写提示词编辑框(右上角「📋 复制」按钮;「📝 提示词」端口接线后自动锁定)
|
||||
* ③ 参数设定显示(只读摘要 4 行:LLM后端/语言/模型 | 增强预设 | 反推预设 | 温度等采样参数)
|
||||
*
|
||||
* 弹窗(导演台同款):居中 overlay + `#1c1c1e` 面板 + header(标题/副标题)+ 内容 + 底部「取消 / 💾 保存」
|
||||
* 弹窗(导演台同款):居中 overlay + `var(--xb-bg)` 面板 + header(标题/副标题)+ 内容 + 底部「取消 / 💾 保存」
|
||||
* · 🤖 大语言模型配置 = 复刻导演台「大语言模型配置」面板(去掉「开关」「故事扩写设置」两组)
|
||||
* · ✨ 提示词增强预设 = 增强预设 / 反推预设 / 追加设定
|
||||
* · 📖 使用说明
|
||||
@@ -89,11 +89,11 @@ function ensureStyle() {
|
||||
const st = el("style", "");
|
||||
st.id = "xbr-style";
|
||||
st.textContent = [
|
||||
".xbr-input{box-sizing:border-box;background:var(--comfy-input-bg,#2a2a2a);color:var(--fg-color,#ddd);" +
|
||||
"border:1px solid var(--border-color,#444);border-radius:4px;padding:6px 10px;font-size:13px;outline:none;font-family:inherit;}",
|
||||
".xbr-input:hover{background:var(--comfy-input-bg-hover,#3a3a3a);}",
|
||||
".xbr-input{box-sizing:border-box;background:var(--comfy-input-bg,var(--xb-bg-raise));color:var(--fg-color,var(--xb-text));" +
|
||||
"border:1px solid var(--border-color,var(--xb-border));border-radius:4px;padding:6px 10px;font-size:13px;outline:none;font-family:inherit;}",
|
||||
".xbr-input:hover{background:var(--comfy-input-bg-hover,var(--xb-border));}",
|
||||
".xbr-input:focus{border-color:#5b9bd5;}",
|
||||
".xbr-input option{background:var(--comfy-menu-bg,#2a2a2a);}",
|
||||
".xbr-input option{background:var(--comfy-menu-bg,var(--xb-bg-raise));}",
|
||||
".xbr-modal input[type='checkbox']{width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;}",
|
||||
".xbr-modal textarea{line-height:1.5;}",
|
||||
].join("\n");
|
||||
@@ -174,7 +174,7 @@ function numberControl(value, opts, onChange) {
|
||||
return i;
|
||||
}
|
||||
function checkboxControl(value, labelText, onChange) {
|
||||
const w = el("label", "display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--fg-color,#ddd);margin-bottom:8px;");
|
||||
const w = el("label", "display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--fg-color,var(--xb-text));margin-bottom:8px;");
|
||||
const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = !!value;
|
||||
@@ -216,8 +216,8 @@ function textareaControl(value, placeholder, rows, onChange) {
|
||||
return t;
|
||||
}
|
||||
function smallBtn(text, css, title) {
|
||||
const b = el("button", "border:1px solid var(--border-color,#555);background:var(--comfy-button-bg,#333);" +
|
||||
"color:var(--fg-color,#eee);border-radius:4px;padding:5px 12px;font-size:12px;cursor:pointer;white-space:nowrap;" + (css || ""), text);
|
||||
const b = el("button", "border:1px solid var(--border-color,var(--xb-border));background:var(--comfy-button-bg,var(--xb-border));" +
|
||||
"color:var(--fg-color,var(--xb-text));border-radius:4px;padding:5px 12px;font-size:12px;cursor:pointer;white-space:nowrap;" + (css || ""), text);
|
||||
b.type = "button";
|
||||
if (title) b.title = title;
|
||||
return b;
|
||||
@@ -301,12 +301,12 @@ function buildUIRow(target) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面"));
|
||||
const mk = (t, d) => {
|
||||
const b = smallBtn(t, "width:22px;height:22px;padding:0;line-height:1;font-size:13px;border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;", d > 0 ? "放大整个界面(不缩放网页/画布)" : "缩小整个界面(不缩放网页/画布)");
|
||||
const b = smallBtn(t, "width:22px;height:22px;padding:0;line-height:1;font-size:13px;border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);", d > 0 ? "放大整个界面(不缩放网页/画布)" : "缩小整个界面(不缩放网页/画布)");
|
||||
b.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
b.addEventListener("click", (e) => { e.stopPropagation(); uiZoomStep(d); uiZoomApply(target); uiZoomSyncLabels(target); });
|
||||
return b;
|
||||
};
|
||||
const val = el("span", "min-width:40px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiZoomGet() * 100) + "%");
|
||||
const val = el("span", "min-width:40px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiZoomGet() * 100) + "%");
|
||||
val.classList.add("xbr-ui-zoom-val");
|
||||
row.append(mk("−", -1), val, mk("+", 1));
|
||||
return row;
|
||||
@@ -479,15 +479,15 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory =
|
||||
ensureStyle();
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,0.78);z-index:9999;display:flex;" +
|
||||
"align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
"max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,0.6);flex:0 0 auto;");
|
||||
dialog.className = "xbr-modal";
|
||||
dialog.setAttribute("role", "dialog");
|
||||
dialog.setAttribute("aria-modal", "true");
|
||||
dialog.setAttribute("aria-label", label);
|
||||
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--border-color,#444);");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:var(--fg-color,#eee);", title));
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--border-color,var(--xb-border));");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:var(--fg-color,var(--xb-text));", title));
|
||||
if (subtitle) header.append(el("div", "font-size:12px;color:var(--descrip-text,#999);margin-top:5px;line-height:1.6;", subtitle));
|
||||
dialog.append(header);
|
||||
|
||||
@@ -495,8 +495,8 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory =
|
||||
dialog.append(body);
|
||||
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;");
|
||||
const footer = el("div", "padding:14px 20px;border-top:1px solid var(--border-color,#444);display:flex;" +
|
||||
"justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const footer = el("div", "padding:14px 20px;border-top:1px solid var(--border-color,var(--xb-border));display:flex;" +
|
||||
"justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
if (footerExtraFactory) {
|
||||
// 导演台同款:底部左侧放「自动保存 + 界面缩放」,与右侧「取消/保存」同行
|
||||
const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;");
|
||||
@@ -507,7 +507,7 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory =
|
||||
footer.append(cancelBtn);
|
||||
let saveBtn = null;
|
||||
if (onSave) {
|
||||
saveBtn = smallBtn(saveLabel, "padding:8px 20px;font-size:14px;font-weight:600;background:#2d5a88;border:none;color:#fff;");
|
||||
saveBtn = smallBtn(saveLabel, "padding:8px 20px;font-size:14px;font-weight:600;background:var(--xb-btn-primary);border:none;color:#fff;");
|
||||
footer.append(saveBtn);
|
||||
}
|
||||
dialog.append(error, footer);
|
||||
@@ -713,7 +713,7 @@ function renderHelpPanel(body) {
|
||||
" · 界面缩放作用于整个弹窗(不缩放网页/画布),Ctrl/⌘ + 滚轮 也可调,记忆在浏览器本地",
|
||||
" · 「取消」丢弃未保存改动;配置随工作流保存;在线 API 的 Key 存在 ComfyUI user 目录,不进工作流",
|
||||
];
|
||||
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")));
|
||||
body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n")));
|
||||
}
|
||||
|
||||
const PANEL_RENDER = { llm: renderLlmPanel, preset: renderPresetPanel, help: renderHelpPanel };
|
||||
@@ -884,7 +884,7 @@ function setupNode(node) {
|
||||
const b = el("button", [
|
||||
"width:100%", `height:${BTN_H}px`, "box-sizing:border-box",
|
||||
"display:flex", "align-items:center", "justify-content:center",
|
||||
"border-radius:6px", "border:2px solid #5b9bd5", "background:#3a3a3a", "color:#eee",
|
||||
"border-radius:6px", "border:2px solid #5b9bd5", "background:var(--xb-border)", "color:var(--xb-text)",
|
||||
"font-size:14px", "cursor:pointer", "white-space:nowrap", "overflow:hidden", "text-overflow:ellipsis",
|
||||
"padding:0 6px", "font-family:inherit",
|
||||
].join(";"));
|
||||
@@ -893,8 +893,8 @@ function setupNode(node) {
|
||||
b.title = `打开「${p.title}」弹窗`;
|
||||
b.dataset.xbrPanelBtn = p.id;
|
||||
b.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "#4a4a4a"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("click", () => openPanelModal(node, p.id));
|
||||
btnGrid.append(b);
|
||||
}
|
||||
@@ -919,7 +919,7 @@ function setupNode(node) {
|
||||
promptWrap.append(promptHead);
|
||||
|
||||
const promptBox = el("textarea", "flex:1 1 auto;min-height:60px;width:100%;box-sizing:border-box;resize:none;line-height:1.5;" +
|
||||
"font-family:inherit;background:#1c1c1e;color:var(--fg-color,#ddd);border:1px solid var(--border-color,#444);" +
|
||||
"font-family:inherit;background:var(--xb-bg);color:var(--fg-color,var(--xb-text));border:1px solid var(--border-color,var(--xb-border));" +
|
||||
"border-radius:6px;padding:8px 10px;font-size:13px;outline:none;");
|
||||
promptBox.value = String(readWidgetValue(wCustom) ?? "");
|
||||
promptBox.placeholder = "在这里写提示词 / 或带 * 预设的占位符内容(如 BBox 检测的目标名称)";
|
||||
@@ -931,7 +931,7 @@ function setupNode(node) {
|
||||
promptTimer = setTimeout(() => { setWidgetValue(wCustom, promptBox.value); node.setDirtyCanvas?.(true, true); }, 250);
|
||||
});
|
||||
promptBox.addEventListener("focus", () => { if (!promptBox.readOnly) promptBox.style.borderColor = "#5b9bd5"; });
|
||||
promptBox.addEventListener("blur", () => { promptBox.style.borderColor = "var(--border-color,#444)"; });
|
||||
promptBox.addEventListener("blur", () => { promptBox.style.borderColor = "var(--border-color,var(--xb-border))"; });
|
||||
promptWrap.append(promptBox);
|
||||
container.append(promptWrap);
|
||||
|
||||
@@ -941,9 +941,9 @@ function setupNode(node) {
|
||||
try {
|
||||
const linked = !!textInput() && textInput().link != null;
|
||||
promptBox.readOnly = linked;
|
||||
promptBox.style.background = linked ? "#141416" : "#1c1c1e";
|
||||
promptBox.style.color = linked ? "#8a8a8a" : "var(--fg-color,#ddd)";
|
||||
promptBox.style.borderColor = linked ? "#5a5a5a" : "var(--border-color,#444)";
|
||||
promptBox.style.background = linked ? "#141416" : "var(--xb-bg)";
|
||||
promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--fg-color,var(--xb-text))";
|
||||
promptBox.style.borderColor = linked ? "#5a5a5a" : "var(--border-color,var(--xb-border))";
|
||||
promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : "";
|
||||
lockHint.textContent = linked
|
||||
? "🔒 手写提示词已锁定(由「📝 提示词」端口控制)"
|
||||
@@ -954,7 +954,7 @@ function setupNode(node) {
|
||||
node.__xbrUpdateLock = updateLock;
|
||||
|
||||
// ⑦ 参数设定显示(只读摘要)
|
||||
const infoBox = el("div", `flex:0 0 auto;height:${MIN_INFO_H}px;overflow:auto;background:#1c1c1e;border:1px solid #333;` +
|
||||
const infoBox = el("div", `flex:0 0 auto;height:${MIN_INFO_H}px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);` +
|
||||
"border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;");
|
||||
infoBox.dataset.captureWheel = "true";
|
||||
container.append(infoBox);
|
||||
|
||||
@@ -1400,15 +1400,15 @@ function el(tag, css, text) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;";
|
||||
|
||||
function makeSectionTitle(text) {
|
||||
return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text);
|
||||
return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text);
|
||||
}
|
||||
|
||||
function field(labelText, control) {
|
||||
const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;");
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText);
|
||||
control.style.flex = "1 1 0";
|
||||
control.style.minWidth = "0";
|
||||
g.append(lab, control);
|
||||
@@ -1435,7 +1435,7 @@ function radioRow(options, value, onChange) {
|
||||
for (const o of options) {
|
||||
const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o);
|
||||
const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o);
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = String(value) === val;
|
||||
@@ -1462,7 +1462,7 @@ function textareaControl(value, onChange, css) {
|
||||
}
|
||||
|
||||
function checkboxControl(value, labelText, onChange, tip) {
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;");
|
||||
const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;");
|
||||
const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;");
|
||||
c.type = "checkbox";
|
||||
c.checked = !!value;
|
||||
@@ -1524,10 +1524,10 @@ function zoomSync(root) {
|
||||
}
|
||||
function buildZoomRow(root) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay()));
|
||||
val.classList.add("ipp-zoom-val");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
zoomSet((zoomGet() == null ? 12 : zoomGet()) + d);
|
||||
zoomApplyContainer(root);
|
||||
@@ -1571,10 +1571,10 @@ window.addEventListener("resize", () => {
|
||||
function buildUIRow(target) {
|
||||
const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;");
|
||||
row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面"));
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%");
|
||||
val.classList.add("ipp-ui-val");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)");
|
||||
const step = (d) => {
|
||||
uiSet(uiGet() + d * UI_STEP);
|
||||
uiApply(target);
|
||||
@@ -1838,12 +1838,12 @@ function openPanel(ctx, panelId) {
|
||||
const title = `${cat.label} 元素`;
|
||||
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;");
|
||||
const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;");
|
||||
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title);
|
||||
const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);");
|
||||
const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title);
|
||||
header.append(titleEl);
|
||||
header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。"));
|
||||
dialog.append(header);
|
||||
|
||||
// ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里
|
||||
@@ -1851,12 +1851,12 @@ function openPanel(ctx, panelId) {
|
||||
const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;");
|
||||
|
||||
// ── 吸顶预览框(不受滚动条约束,始终在画面内) ──
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;");
|
||||
const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);");
|
||||
const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;");
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)"));
|
||||
const headCount = el("span", "font-size:11px;color:#888;", "");
|
||||
head.append(headCount);
|
||||
const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;");
|
||||
const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;");
|
||||
preview.spellcheck = false;
|
||||
preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…";
|
||||
@@ -1897,10 +1897,10 @@ function openPanel(ctx, panelId) {
|
||||
|
||||
// ── 底部 ──
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
autoCb.type = "checkbox";
|
||||
autoCb.checked = settings.auto_save !== false;
|
||||
@@ -1985,13 +1985,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不
|
||||
* ============================================================ */
|
||||
function openConfirm(title, message, onOk, okText) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "confirm";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认"));
|
||||
box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || ""));
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} });
|
||||
@@ -2009,29 +2009,29 @@ function openConfirm(title, message, onOk, okText) {
|
||||
* ============================================================ */
|
||||
function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "rel";
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;",
|
||||
`道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`));
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;");
|
||||
const close = () => { try { overlay.remove(); } catch (_) {} };
|
||||
for (const [id, zh, en] of RELATIONS) {
|
||||
const on = id === current;
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`);
|
||||
const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`);
|
||||
b.dataset.rel = id;
|
||||
b.title = `${zh} / ${en}`;
|
||||
b.append(el("span", "font-size:13px;line-height:1.2;", zh));
|
||||
b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en));
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; });
|
||||
b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} });
|
||||
grid.append(b);
|
||||
}
|
||||
box.append(grid);
|
||||
const row = el("div", "display:flex;gap:10px;justify-content:flex-end;");
|
||||
const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} });
|
||||
cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); });
|
||||
overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); });
|
||||
@@ -2049,10 +2049,10 @@ function openRelPicker(node, nameText, lang, current, onPick) {
|
||||
* ============================================================ */
|
||||
function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试)
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述"));
|
||||
box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;",
|
||||
`词条:${nameText}\n`
|
||||
+ "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发)),"
|
||||
+ "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。"));
|
||||
@@ -2064,9 +2064,9 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存");
|
||||
box.append(tip);
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述");
|
||||
row.append(clrBtn, cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -2090,11 +2090,11 @@ function openDescEditor(node, entry, nameText, value, onSave) {
|
||||
/** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */
|
||||
function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;");
|
||||
const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;");
|
||||
const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;");
|
||||
box.dataset.ippModal = "slotname"; // 稳定锤点
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)"));
|
||||
const mk = (label, val, ph) => {
|
||||
box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label));
|
||||
box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label));
|
||||
const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;");
|
||||
i.type = "text";
|
||||
i.value = val || "";
|
||||
@@ -2105,8 +2105,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) {
|
||||
const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发");
|
||||
const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair");
|
||||
const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称");
|
||||
row.append(cancelBtn, okBtn);
|
||||
box.append(row);
|
||||
overlay.append(box);
|
||||
@@ -2141,12 +2141,12 @@ function renderCategoryPanel(box, api, catId) {
|
||||
catRow.dataset.ippCatRow = "1";
|
||||
for (const c of CATEGORIES) {
|
||||
const on = c.id === catId;
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label);
|
||||
b.dataset.cat = c.id;
|
||||
if (on) b.dataset.catActive = "1";
|
||||
b.title = `切到「${c.label}」面板 —— ${c.tip}`;
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; });
|
||||
b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; });
|
||||
b.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (!on && api.switchCategory) api.switchCategory(c.id);
|
||||
@@ -2156,7 +2156,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
box.append(catRow);
|
||||
const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;");
|
||||
bar.append(
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => {
|
||||
openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => {
|
||||
s.elements.selected = {};
|
||||
s.elements.rel = {};
|
||||
@@ -2166,17 +2166,17 @@ function renderCategoryPanel(box, api, catId) {
|
||||
notify("已清空提示词", "success");
|
||||
});
|
||||
}),
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => {
|
||||
const t = rebuildBody(s, index, lang());
|
||||
bodySet(t);
|
||||
fire();
|
||||
notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning");
|
||||
}),
|
||||
smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => {
|
||||
smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => {
|
||||
copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io));
|
||||
}),
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)),
|
||||
smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => {
|
||||
if (!obj || typeof obj !== "object") return;
|
||||
for (const k of ["custom", "noted", "selected"]) {
|
||||
if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]);
|
||||
@@ -2208,7 +2208,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
// 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态)
|
||||
if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" };
|
||||
const ui = CAT_UI[cat.id];
|
||||
const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;");
|
||||
const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;");
|
||||
const selOf = () => (s.elements.selected[cat.id] || []);
|
||||
const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = []));
|
||||
const rebuild = () => { wrap.innerHTML = ""; build(); };
|
||||
@@ -2283,14 +2283,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const cur = subOf(cat, ui.sub).id;
|
||||
for (const c of chipRefs) {
|
||||
const on = c.dataset.sub === cur;
|
||||
c.style.background = on ? "#2d5a88" : "#2a2a2a";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "#555";
|
||||
c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)";
|
||||
c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)";
|
||||
c.style.color = on ? "#eaf4ff" : "#bbb";
|
||||
c.style.fontWeight = on ? "600" : "400";
|
||||
}
|
||||
};
|
||||
for (const sub of (cat.subs || [])) {
|
||||
const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`);
|
||||
b.dataset.sub = sub.id;
|
||||
b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`;
|
||||
b.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
@@ -2340,7 +2340,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
fire();
|
||||
notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning");
|
||||
};
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => {
|
||||
const list = visibleEntries();
|
||||
if (!list.length) { notify("没有可加入的词条", "warning"); return; }
|
||||
if (list.length > 60) {
|
||||
@@ -2349,7 +2349,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}
|
||||
addAllNow(list);
|
||||
}));
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => {
|
||||
tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => {
|
||||
const keys = selOf().slice();
|
||||
if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; }
|
||||
removeKeys(keys);
|
||||
@@ -2379,7 +2379,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(entry.key);
|
||||
const descKey = `${cat.id}|${entry.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
// 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip)
|
||||
row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}`
|
||||
+ (entry.custom ? "\n(自建槽位)" : "")
|
||||
@@ -2398,14 +2398,14 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderRows();
|
||||
fire();
|
||||
});
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
const rel = relPair(s, cat.id, entry.key);
|
||||
if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh));
|
||||
const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`);
|
||||
row.append(nm);
|
||||
if (cat.id === "props") {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => {
|
||||
openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(),
|
||||
(relPair(s, cat.id, entry.key) || {}).id || "",
|
||||
(relId) => {
|
||||
@@ -2417,7 +2417,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
}));
|
||||
}
|
||||
const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2456,7 +2456,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
const selected = selOf().includes(it.key);
|
||||
const descKey = `${cat.id}|${it.key}`;
|
||||
const desc = s.elements.noted[descKey] || "";
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`);
|
||||
const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`);
|
||||
row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)`
|
||||
+ `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}`
|
||||
+ "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)");
|
||||
@@ -2466,10 +2466,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
});
|
||||
row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建"));
|
||||
row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`,
|
||||
`${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`));
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => {
|
||||
openSlotNameEditor(it.zh, it.en, (z, e2) => {
|
||||
it.zh = z; it.en = e2;
|
||||
if (entry) { entry.zh = z; entry.en = e2; }
|
||||
@@ -2479,7 +2479,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
});
|
||||
}));
|
||||
row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述",
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
`border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`,
|
||||
desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)",
|
||||
() => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => {
|
||||
const v = String(text || "").trim();
|
||||
@@ -2494,7 +2494,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
api.refreshPreview(null);
|
||||
fire();
|
||||
})));
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => {
|
||||
openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => {
|
||||
removeKeys([it.key]);
|
||||
delete s.elements.noted[`${cat.id}|${it.key}`];
|
||||
@@ -2512,7 +2512,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
renderCustom();
|
||||
|
||||
const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;");
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => {
|
||||
const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []);
|
||||
arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" });
|
||||
index = buildIndex(s.elements);
|
||||
@@ -2520,7 +2520,7 @@ function renderCategoryPanel(box, api, catId) {
|
||||
rebuild();
|
||||
fire();
|
||||
}));
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => {
|
||||
const arr = s.elements.custom[cat.id] || [];
|
||||
if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; }
|
||||
const last = arr[arr.length - 1];
|
||||
@@ -2543,10 +2543,10 @@ function renderCategoryPanel(box, api, catId) {
|
||||
/** 分类外壳:标题栏总是建,内容懒建(展开才建) */
|
||||
function build() {
|
||||
wrap.innerHTML = "";
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:#8a97a5;", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt,
|
||||
const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;");
|
||||
const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼");
|
||||
const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText());
|
||||
headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt,
|
||||
el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || ""));
|
||||
const body = el("div", "padding:8px 10px;");
|
||||
body.style.display = ui.collapsed ? "none" : "block";
|
||||
@@ -2811,11 +2811,11 @@ function setupNode(node) {
|
||||
const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const b of PANEL_BUTTONS) {
|
||||
const cat = catOf(b.panel);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`;
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const settings = loadSettings(node);
|
||||
@@ -2914,9 +2914,9 @@ function setupNode(node) {
|
||||
box.style.margin = "0 8px";
|
||||
box.style.boxSizing = "border-box";
|
||||
box.style.borderRadius = "4px";
|
||||
box.style.border = "1px solid #444";
|
||||
box.style.background = "#1d1d1d";
|
||||
box.style.color = "#ccc";
|
||||
box.style.border = "1px solid var(--xb-border)";
|
||||
box.style.background = "var(--xb-bg)";
|
||||
box.style.color = "var(--xb-text)";
|
||||
box.style.padding = "5px 8px";
|
||||
box.style.fontSize = "12px";
|
||||
box.style.lineHeight = "1.6";
|
||||
@@ -3453,7 +3453,7 @@ function xbrTextControl(value, placeholder, onChange) {
|
||||
/** 导演台同款「种子 + 生成后控制(随机/固定/增加/减少)」一行 */
|
||||
function xbrSeedRow(run, onChange) {
|
||||
const g = el("div", "display:flex;align-items:center;gap:10px;margin-bottom:10px;");
|
||||
g.append(el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子"));
|
||||
g.append(el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子"));
|
||||
const i = xbrNumberControl(run.seed, { min: 0, max: Number.MAX_SAFE_INTEGER, step: 1 }, (v) => { run.seed = Math.round(v); onChange?.(); });
|
||||
i.style.flex = "1 1 0";
|
||||
const ctrl = smallBtn("", "flex:1 1 0;min-width:0;background:#b45309;border:1px solid #d97706;color:#fff;text-align:center;", "生成后控制:点击在 随机 / 固定 / 增加 / 减少 间切换", null);
|
||||
@@ -3476,24 +3476,24 @@ function xbrSeedRow(run, onChange) {
|
||||
let xbrModal = null;
|
||||
function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, onCommit = null, onImmediate = null, onChange = null, onClose = null }) {
|
||||
const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;align-items:center;justify-content:center;overflow:auto;");
|
||||
const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
"max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;");
|
||||
const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` +
|
||||
"max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;");
|
||||
dialog.dataset.xbrProModal = label;
|
||||
dialog.setAttribute("role", "dialog");
|
||||
dialog.setAttribute("aria-modal", "true");
|
||||
dialog.setAttribute("aria-label", label);
|
||||
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid #444;");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:#eee;", title));
|
||||
if (subtitle) header.append(el("div", "font-size:12px;color:#999;margin-top:5px;line-height:1.6;", subtitle));
|
||||
const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--xb-border);");
|
||||
header.append(el("div", "font-size:17px;font-weight:700;color:var(--xb-text);", title));
|
||||
if (subtitle) header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;line-height:1.6;", subtitle));
|
||||
const body = el("div", "padding:16px 20px;overflow-y:auto;flex:1;min-height:200px;");
|
||||
const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;");
|
||||
const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;");
|
||||
|
||||
let autoCb = null;
|
||||
if (autoSaveRef) {
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;");
|
||||
const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;");
|
||||
autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;");
|
||||
autoCb.type = "checkbox";
|
||||
autoCb.checked = autoSaveRef.value !== false;
|
||||
@@ -3508,8 +3508,8 @@ function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, on
|
||||
uiRow.title = "调整整个弹窗界面大小(不缩放网页/画布;Ctrl/⌘+滚轮 也可)";
|
||||
left.append(zoomRow, uiRow);
|
||||
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = onCommit ? el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null;
|
||||
const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消");
|
||||
const saveBtn = onCommit ? el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null;
|
||||
footer.append(left, cancelBtn);
|
||||
if (saveBtn) footer.append(saveBtn);
|
||||
dialog.append(header, body, error, footer);
|
||||
@@ -3668,7 +3668,7 @@ function xbrRenderPreset(body, node, draft, ctx) {
|
||||
body.append(taP);
|
||||
const taRow = el("div", "display:flex;align-items:center;gap:10px;margin:6px 0 4px;");
|
||||
if (modeDef) {
|
||||
taRow.append(smallBtn("♻️ 恢复默认", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:4px 10px;",
|
||||
taRow.append(smallBtn("♻️ 恢复默认", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;",
|
||||
"把设定词恢复为该模式 × 该模版 × 该语言的默认文本",
|
||||
() => { draft.presetText = modeDef; draft.presetTouched = false; ctx.rerender(); }));
|
||||
}
|
||||
@@ -3705,7 +3705,7 @@ function xbrRenderPreset(body, node, draft, ctx) {
|
||||
"不用:SKILL 完全不生效(即使下面选过文件也不会送进模型)。"));
|
||||
}
|
||||
const foxRow = el("div", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:2px 0 8px;");
|
||||
foxRow.append(smallBtn("📂 打开 SKILL 文件夹", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:4px 10px;",
|
||||
foxRow.append(smallBtn("📂 打开 SKILL 文件夹", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;",
|
||||
"在资源管理器里打开 support_llama/skills,方便新增 / 编辑技能文件",
|
||||
async () => {
|
||||
try {
|
||||
@@ -3867,7 +3867,7 @@ function xbrRenderHelp(body) {
|
||||
" · 弹窗底部:☑ 自动保存(默认开)、字号 A− / A+、界面 − / %",
|
||||
" · 所有配置随工作流保存;「取消」丢弃未保存改动",
|
||||
];
|
||||
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")));
|
||||
body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n")));
|
||||
}
|
||||
|
||||
const XBR_PANEL_RENDER = { llm: xbrRenderLlm, preset: xbrRenderPreset, help: xbrRenderHelp };
|
||||
@@ -3986,12 +3986,12 @@ async function xbrOpenModal(node, panelId) {
|
||||
function xbrBuildButtonRow(node) {
|
||||
const grid = el("div", "display:grid;grid-template-columns:repeat(" + Math.max(1, XBR_PANEL_BUTTONS.length) + ",1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;");
|
||||
for (const b of XBR_PANEL_BUTTONS) {
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label);
|
||||
btn.title = `打开「${b.title}」弹窗`;
|
||||
btn.dataset.xbrProBtn = b.id;
|
||||
btn.addEventListener("mousedown", (e) => e.stopPropagation());
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; });
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; });
|
||||
btn.addEventListener("click", (e) => { e.stopPropagation(); xbrOpenModal(node, b.id); });
|
||||
grid.append(btn);
|
||||
}
|
||||
@@ -4027,11 +4027,11 @@ function xbrInfoLines(node, promptText) {
|
||||
/** 提示词框标题行(左标签 + 右复制)+ 参数设定显示;并挂上端口锁定/执行回写/摘要刷新 */
|
||||
function xbrBuildPromptHeader(node, promptBox) {
|
||||
const head = el("div", "display:flex;align-items:center;gap:8px;flex:0 0 auto;");
|
||||
const lab = el("div", "font-size:12px;color:#bbb;", "📝 节点提示词(与面板预览框双向同步)");
|
||||
const lab = el("div", "font-size:12px;color:var(--xb-text-dim);", "📝 节点提示词(与面板预览框双向同步)");
|
||||
const copy = smallBtn("📋 复制", "margin-left:auto;", "复制提示词框内容", () => { copyText(promptBox.value || ""); });
|
||||
head.append(lab, copy);
|
||||
|
||||
const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:#1c1c1e;border:1px solid #333;border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;");
|
||||
const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;");
|
||||
infoBox.dataset.captureWheel = "true";
|
||||
infoBox.title = "当前配置摘要(只读)";
|
||||
|
||||
@@ -4045,8 +4045,8 @@ function xbrBuildPromptHeader(node, promptBox) {
|
||||
const inp = (node.inputs || []).find((i) => i.name === "text");
|
||||
const linked = !!inp && inp.link != null;
|
||||
promptBox.readOnly = linked;
|
||||
promptBox.style.background = linked ? "#141416" : "#1d1d1d";
|
||||
promptBox.style.color = linked ? "#8a8a8a" : "#ccc";
|
||||
promptBox.style.background = linked ? "#141416" : "var(--xb-bg)";
|
||||
promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--xb-text)";
|
||||
promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : "";
|
||||
lab.textContent = linked ? "📝 提示词已锁定(由「📝 提示词」端口控制)" : "📝 节点提示词(与面板预览框双向同步)";
|
||||
lab.style.color = linked ? "#d9a441" : "#bbb";
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* XB_ToolBox — 主题变量
|
||||
* ============================================================
|
||||
* 插件的 DOM widget(提示词面板、选择器、弹窗等)里的大量内联样式原本写死深色,
|
||||
* 在 ComfyUI 浅色主题下会变成"黑块"。这里统一注入一组 CSS 变量,
|
||||
* 各 widget 改用 var(--xb-*) 后即可自动跟随明暗主题。
|
||||
*
|
||||
* ComfyUI 通过调色板设置切换主题(不同版本可能改 <html> / <body> 的 class),
|
||||
* 所以这里监听 class 变化实时更新,无需刷新页面。
|
||||
*/
|
||||
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const PALETTE = {
|
||||
// 名称: [深色值, 浅色值]
|
||||
"--xb-bg": ["#1c1c1e", "#ffffff"],
|
||||
"--xb-bg-soft": ["#232326", "#f2f3f5"],
|
||||
"--xb-bg-raise": ["#2b2b2b", "#e9eaee"],
|
||||
"--xb-border": ["#3a3a3a", "#c9ced6"],
|
||||
"--xb-text": ["#dddddd", "#23262a"],
|
||||
"--xb-text-dim": ["#8a97a5", "#5c626b"],
|
||||
"--xb-accent-bg": ["#2d5a88", "#2f6fbf"],
|
||||
"--xb-info": ["#cfe3f7", "#1b5e9c"],
|
||||
"--xb-ok": ["#d7f0c8", "#1f7a3a"],
|
||||
"--xb-warn": ["#ffd98a", "#8a5f10"],
|
||||
"--xb-error": ["#ffd0d0", "#a32626"],
|
||||
"--xb-orange": ["#e8a87c", "#8a4a1f"],
|
||||
"--xb-on-accent": ["#eaf4ff", "#ffffff"],
|
||||
"--xb-tint-info": ["#2a4a6a", "#e3edf9"],
|
||||
"--xb-tint-warn": ["#5a4a2a", "#fdf1d6"],
|
||||
"--xb-tint-ok": ["#3a5a2a", "#e7f5e9"],
|
||||
"--xb-tint-error": ["#5a2a2a", "#fdeaea"],
|
||||
"--xb-btn-primary": ["#2d5a88", "#1f6fd0"],
|
||||
"--xb-inset": ["#191919", "#f5f6f8"],
|
||||
"--xb-float": ["#2a2a2e", "#ffffff"],
|
||||
"--xb-btn-info": ["#2a4a6a", "#1f6fd0"],
|
||||
"--xb-btn-warn": ["#b45309", "#c2410c"],
|
||||
"--xb-btn-ok": ["#2d5a3a", "#1f7a3a"],
|
||||
"--xb-btn-error": ["#a33a3a", "#b3261e"],
|
||||
"--xb-accent-bg-hover": ["#22456b", "#1a5cb0"],
|
||||
"--xb-accent-text": ["#eaf4ff", "#ffffff"],
|
||||
};
|
||||
|
||||
function prefersLight() {
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
if (html?.classList?.contains("light-theme")) return true;
|
||||
if (html?.classList?.contains("dark-theme")) return false;
|
||||
if (body?.classList?.contains("light-theme")) return true;
|
||||
if (body?.classList?.contains("dark-theme")) return false;
|
||||
try {
|
||||
const palette = app?.ui?.settings?.getSettingValue?.("Comfy.ColorPalette");
|
||||
if (palette === "light") return true;
|
||||
if (palette === "dark") return false;
|
||||
} catch (_) { /* ignore */ }
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
const light = prefersLight();
|
||||
const root = document.documentElement;
|
||||
for (const [name, [dark, bright]] of Object.entries(PALETTE)) {
|
||||
root.style.setProperty(name, light ? bright : dark);
|
||||
}
|
||||
root.dataset.xbTheme = light ? "light" : "dark";
|
||||
}
|
||||
|
||||
let installed = false;
|
||||
|
||||
function install() {
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
applyTheme();
|
||||
|
||||
const observer = new MutationObserver(applyTheme);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class", "data-theme"],
|
||||
});
|
||||
if (document.body) {
|
||||
observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "XB_ToolBox.Theme",
|
||||
async setup() {
|
||||
install();
|
||||
},
|
||||
});
|
||||
@@ -31,7 +31,7 @@ app.registerExtension({
|
||||
const el = wDur.inputEl || wDur.element;
|
||||
if (el) {
|
||||
el.readOnly = true;
|
||||
el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;";
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -56,14 +56,14 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const ctr = document.createElement("div");
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" });
|
||||
|
||||
const audioEl = document.createElement("audio");
|
||||
audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;";
|
||||
ctr.appendChild(audioEl);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;";
|
||||
canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;";
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = 1200 * dpr; canvas.height = 80 * dpr;
|
||||
ctr.appendChild(canvas);
|
||||
|
||||
@@ -164,7 +164,7 @@ function makeButton(node, w, item) {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = label;
|
||||
if (tip) button.title = tip;
|
||||
button.style.cssText = "padding:7px 6px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;cursor:pointer;font-size:12px;line-height:1.25;text-align:left;";
|
||||
button.style.cssText = "padding:7px 6px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);cursor:pointer;font-size:12px;line-height:1.25;text-align:left;";
|
||||
button.addEventListener("mouseenter", () => { button.style.background = "#3a3a40"; });
|
||||
button.addEventListener("mouseleave", () => { button.style.background = "#2a2a2e"; });
|
||||
button.addEventListener("click", () => {
|
||||
@@ -202,10 +202,10 @@ function addCustomTagRow(panel, node, w) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.placeholder = "例如 Rap / 高潮 / 吟唱";
|
||||
input.style.cssText = "flex:1;padding:7px 8px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;font-size:12px;";
|
||||
input.style.cssText = "flex:1;padding:7px 8px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);font-size:12px;";
|
||||
const button = document.createElement("button");
|
||||
button.textContent = "插入标签";
|
||||
button.style.cssText = "padding:7px 12px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;cursor:pointer;font-size:12px;";
|
||||
button.style.cssText = "padding:7px 12px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);cursor:pointer;font-size:12px;";
|
||||
const commit = () => {
|
||||
const raw = input.value.trim().replace(/^\[|\]$/g, "").trim();
|
||||
if (!raw) return;
|
||||
@@ -228,13 +228,13 @@ function showTagPicker(node, w) {
|
||||
overlay.addEventListener("mousedown", (event) => { if (event.target === overlay) closeTagPicker(); });
|
||||
|
||||
const panel = document.createElement("div");
|
||||
panel.style.cssText = "background:#1b1b1f;border:1px solid #444;border-radius:10px;padding:14px;max-width:700px;width:92%;max-height:86vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);";
|
||||
panel.style.cssText = "background:#1b1b1f;border:1px solid var(--xb-border);border-radius:10px;padding:14px;max-width:700px;width:92%;max-height:86vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);";
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.textContent = "添加元素";
|
||||
title.title = "点击标签插入到光标处。段落标签会独占一行并自动换行;"
|
||||
+ "行级角色标注直接插在光标处,接着写歌词即可。";
|
||||
title.style.cssText = "color:#eee;font-size:14px;font-weight:600;margin-bottom:8px;cursor:help;";
|
||||
title.style.cssText = "color:var(--xb-text);font-size:14px;font-weight:600;margin-bottom:8px;cursor:help;";
|
||||
panel.appendChild(title);
|
||||
|
||||
addGroup(panel, "① 官方段落词表(8 个)", GROUP_TIPS.OFFICIAL,
|
||||
|
||||