Files
da2el-ai-D2-nodes-ComfyUI/web/D2_PromptConverDialog.js
T
da2el-alandClaude Opus 4.8 8f7039fcf8 feat: apply ComfyUI button styles to float panel buttons
Define shared button CSS class constants (BASE / PRIMARY / SECONDARY)
in D2_FloatContainer and apply them to the queue and prompt-convert
buttons so they match the ComfyUI (PrimeVue/Tailwind) button look.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 02:08:09 +09:00

362 lines
12 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { D2_FloatContainer, CSS_CLASS_BUTTON_BASE, CSS_CLSSS_BUTTON_PRIMARY, CSS_CLSSS_BUTTON_SECONDARY } from "./D2_FloatContainer.js";
import { loadCssFile } from "./modules/utils.js";
/**
* プロンプト変換
*/
class D2_PromptConvert {
static RATE = 1.05;
/**
* StableDiffusionのweightをNAI方式に変換
*/
static convertToNai (srcPrompt, convertType = "old") {
// 改行位置を記録するためのプレースホルダーを使用
const lineBreakPlaceholder = '___LINEBREAK___';
const tempPrompt = srcPrompt.replace(/\n/g, lineBreakPlaceholder);
const convertedPrompt = tempPrompt.replace(/\([^)]+:\s*[0-9.]+\s*\)/g, (match) => {
return D2_PromptConvert.$_convertToNai(match, convertType);
});
// 改行を復元
return convertedPrompt.replace(new RegExp(lineBreakPlaceholder, 'g'), '\n');
}
static $_convertToNai (prompt, convertType = "old") {
const parts = prompt.substring(1, prompt.length - 1).split(':');
const text = parts.slice(0, -1).join(':').trim();
const weightStr = parts[parts.length - 1].trim();
const weight = parseFloat(weightStr);
if (convertType === "new") {
// 新方式: 1.2::aaa:: という変換
return `${weight}::${text}::`;
} else {
// 旧方式: (aaa:1.2) を {{{aaa}}} という変換
const n = Math.round(Math.log(weight) / Math.log(D2_PromptConvert.RATE));
const count = Math.abs(n);
let openBra, closeBra;
if (weight < 1) {
openBra = '[';
closeBra = ']';
} else {
openBra = '{';
closeBra = '}';
}
return openBra.repeat(count) + text + closeBra.repeat(count);
}
}
/**
* NAIのweightをStableDiffusion方式に変換
*/
static convertToSd (srcPrompt, convertType = "old") {
// 改行位置を記録するためのプレースホルダーを使用
const lineBreakPlaceholder = '___LINEBREAK___';
let tempPrompt = srcPrompt.replace(/\n/g, lineBreakPlaceholder);
if (convertType === "new") {
// 新方式: 1.2::aaa:: を (aaa:1.2) に変換
tempPrompt = tempPrompt.replace(/([0-9.]+)::(.+?)::/g, (match) => {
return D2_PromptConvert.$_convertToSd(match, convertType);
});
} else {
// 旧方式: {{{aaa}}} を (aaa:1.2) に変換
tempPrompt = tempPrompt.replace(/[\[{]+[^\]}]+[\]}]+/g, (match) => {
return D2_PromptConvert.$_convertToSd(match, convertType);
});
}
// 改行を復元
return tempPrompt.replace(new RegExp(lineBreakPlaceholder, 'g'), '\n');
}
static $_convertToSd (prompt, convertType = "old") {
if (convertType === "new") {
// 新方式: 1.2::aaa:: を (aaa:1.2) に変換
const match = prompt.match(/([0-9.]+)::(.+?)::/);
if (match) {
const weight = parseFloat(match[1]);
const text = match[2].trim();
return `(${text}:${weight})`;
}
return prompt;
} else {
// 旧方式: {{{aaa}}} を (aaa:1.2) に変換
const braType = prompt.substring(0, 1);
const braCount = (prompt.match(/[\[{]/g) || []).length;
let weight = 0;
if (braType === '{') {
weight = 1 * Math.pow(D2_PromptConvert.RATE, braCount);
} else {
weight = 1 * Math.pow(1 / D2_PromptConvert.RATE, braCount);
}
let weightAdjust;
// 四捨五入して小数点2位にするか、
// 四捨五入せず小数点3位で切り捨てるか
weightAdjust = Math.round(weight * 10) / 10;
// if (opts.d2_npc_enable_rounding) {
// weightAdjust = Math.round(weight * 10) / 10;
// } else {
// weightAdjust = Math.floor(weight * 100) / 100;
// }
const text = prompt.replace(/[\[\]{}]+/g, '');
return `(${text}:${weightAdjust})`;
}
}
}
/////////////////////////////////////////////
/////////////////////////////////////////////
/////////////////////////////////////////////
/**
* プロンプト変換ダイアログ
*/
class D2_PromptConvertDialog {
static CSS_FILEPATH = "/D2/assets/css/D2_PromptConvertDialog.css";
container = undefined;
convertType = "new"; // デフォルト値
constructor() {
this._createDialog();
loadCssFile(D2_PromptConvertDialog.CSS_FILEPATH);
}
/**
* モーダルの表示
*/
showModal () {
// this.container.showModal();
this.container.style.display = "block";
}
_createDialog () {
this.container = document.createElement("div");
this.container.classList.add("comfy-modal", "d2-prompt-convert");
document.body.appendChild(this.container);
const content = document.createElement("div");
content.classList.add("d2-prompt-convert__content");
this.container.appendChild(content);
const sdPrompt = D2_PromptConvertDialog.createPromptArea("input StableDiffusion Prompt");
content.appendChild(sdPrompt);
const naiPrompt = D2_PromptConvertDialog.createPromptArea("Input NovelAI Prompt");
content.appendChild(naiPrompt);
// SD > NAI 変換ボタン
const sdToNaiBtn = D2_PromptConvertDialog.createButton("SD 👉 NAI", () => {
const prompt = sdPrompt.value;
const newPrompt = D2_PromptConvert.convertToNai(prompt, this.convertType);
naiPrompt.value = newPrompt;
});
content.appendChild(sdToNaiBtn);
// NAI > SD 変換ボタン
const naiToSdBtn = D2_PromptConvertDialog.createButton("SD 👈 NAI", () => {
const prompt = naiPrompt.value;
const newPrompt = D2_PromptConvert.convertToSd(prompt, this.convertType);
sdPrompt.value = newPrompt;
});
content.appendChild(naiToSdBtn);
// SDクリップボード
const copySdBtn = D2_PromptConvertDialog.createButton("📋 Copy SD and ❌ close", () => {
sdPrompt.select();
document.execCommand("copy");
this.container.style.display = "none";
});
content.appendChild(copySdBtn);
// NAIクリップボード
const copyNaiBtn = D2_PromptConvertDialog.createButton("📋 Copy NAI and ❌ close", () => {
naiPrompt.select();
document.execCommand("copy");
this.container.style.display = "none";
});
content.appendChild(copyNaiBtn);
// 閉じるボタン
const closeBtn = document.createElement("button");
closeBtn.classList.add("d2-prompt-convert__close-btn");
closeBtn.textContent = "❌ Close";
closeBtn.addEventListener("click", () => {
// container.close();
this.container.style.display = "none";
});
content.appendChild(closeBtn);
// 方式切り替えラジオボタン
const radioButtons = this.createRadioButtons();
content.appendChild(radioButtons);
}
/**
* プロンプトエリア作成
* @param {string} placeholder
* @returns HTMLElement
*/
static createPromptArea (placeholder) {
const textArea = document.createElement("textarea");
textArea.classList.add("comfy-multiline-input", "d2-prompt-convert__prompt-area");
textArea.placeholder = placeholder;
return textArea;
}
/**
* ボタン作成
* @param {string} text
* @param {function} onClick
* @returns
*/
static createButton (text, onClick) {
const btn = document.createElement("button");
btn.classList.add("d2-prompt-convert__btn");
btn.textContent = text;
btn.addEventListener("click", () => {
onClick();
});
return btn;
}
/**
* ラジオボタンコンテナ作成
* @returns HTMLElement
*/
createRadioButtons () {
const radioContainer = document.createElement("div");
radioContainer.classList.add("d2-prompt-convert__radio-container");
radioContainer.style.cssText = `
margin: 10px 0;
display: flex;
gap: 20px;
align-items: center;
justify-content: center;
grid-column: span 2;
`;
const oldTypeLabel = document.createElement("label");
oldTypeLabel.htmlFor = "oldType";
oldTypeLabel.textContent = "Old Type";
oldTypeLabel.style.cssText = `
margin-left: 5px;
cursor: pointer;
color: var(--input-text);
`;
const oldTypeRadio = document.createElement("input");
oldTypeRadio.type = "radio";
oldTypeRadio.name = "convertType";
oldTypeRadio.id = "oldType";
oldTypeRadio.value = "old";
oldTypeRadio.checked = false;
oldTypeRadio.addEventListener("change", () => {
if (oldTypeRadio.checked) {
this.convertType = "old";
}
});
oldTypeLabel.prepend(oldTypeRadio);
const newTypeLabel = document.createElement("label");
newTypeLabel.htmlFor = "newType";
newTypeLabel.textContent = "New Type";
newTypeLabel.style.cssText = `
margin-left: 5px;
cursor: pointer;
color: var(--input-text);
`;
const newTypeRadio = document.createElement("input");
newTypeRadio.type = "radio";
newTypeRadio.name = "convertType";
newTypeRadio.id = "newType";
newTypeRadio.value = "new";
newTypeRadio.checked = true;
newTypeRadio.addEventListener("change", () => {
if (newTypeRadio.checked) {
this.convertType = "new";
}
});
newTypeLabel.prepend(newTypeRadio);
radioContainer.appendChild(newTypeLabel);
radioContainer.appendChild(oldTypeLabel);
return radioContainer;
}
}
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
/**
* ダイアログ表示ボタン
*/
class D2_PromptConvertButton {
floatContainer = undefined;
dialog = undefined;
constructor() {
// フロートコンテナ
this.floatContainer = new D2_FloatContainer("D2_PromptConvertBUtton", 50, 100);
// ダイアログ
this.dialog = new D2_PromptConvertDialog();
this._createButton();
// 表示切り替え
const visible = app.ui.settings.getSettingValue("D2.PromptConvertButton.Visible", false);
this.changeVisible(visible);
}
/**
* 表示切り替え
*/
changeVisible (bool) {
this.floatContainer.changeVisible(bool);
}
/**
* ボタン作成
*/
_createButton () {
const button = document.createElement("button");
button.classList.add("p-button", ...CSS_CLASS_BUTTON_BASE.split(" "), ...CSS_CLSSS_BUTTON_PRIMARY.split(" "));
button.textContent = "Prompt convert";
this.floatContainer.addContent(button);
button.addEventListener("click", () => {
this.dialog.showModal();
});
}
}
(function () {
const promptConvertButton = new D2_PromptConvertButton();
app.ui.settings.addSetting({
id: "D2.PromptConvertButton.Visible",
name: "Show prompt convert button",
type: "boolean",
defaultValue: false,
onChange (value) {
promptConvertButton.changeVisible(value);
},
});
})();