diff --git a/README.md b/README.md
index c64d8b5..67c20a3 100644
--- a/README.md
+++ b/README.md
@@ -479,6 +479,19 @@ Output text
### D2 Refiner Steps Tester
- A node for checking steps
+
+### Prompt convert dialog
+
+
+
+- Dialog for converting weights between NovelAI and StableDiffusion formats
+- Open from the `Prompt convert` button
+- Hidden by default
+- Enable `settings > D2 > Show prompt convert button` to display
+
+
+
+
## :card_index_dividers: Sample Workflow
You can recreate the workflow by dropping the image into ComfyUI.
@@ -514,6 +527,7 @@ In the custom_nodes folder, `git clone`
**2024.10.28**
+- Prompt convert: Added a dialog for converting prompts between NovelAI and StableDiffusion formats
- D2 Folder Image Queue: Fixed a bug where the number of generated images was not consistent
**2024.10.26**
diff --git a/README_ja.md b/README_ja.md
index f85ff8f..00fa893 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -480,6 +480,21 @@ Output text
- steps を確認するためのノード
+
+
+### Prompt convert dialog
+
+
+
+- NovelAI と StableDiffusion の weight を相互変換するダイアログ
+- `Prompt convert` ボタンから開く
+- 初期状態では非表示になっている
+- `settings > D2 > Show prompt convert button` を有効にすると表示する
+
+
+
+
+
## :card_index_dividers: Sample Workflow
画像を ComfyUI にドロップするとワークフローを再現できます。
@@ -522,6 +537,7 @@ custom_nodes フォルダで `git clone` する
**2024.10.28**
+- Prompt convert: NovelAI と StableDiffusion のプロンプトを相互変換するダイアログを追加
- D2 Folder Image Queue: 画像生成枚数が同じにならない不具合を修正
**2024.10.26**
diff --git a/README_zh.md b/README_zh.md
index d08b744..2ca4715 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -476,6 +476,18 @@ Output text
### D2 Refiner Steps Tester
- 用於確認步數的節點
+
+### Prompt convert dialog
+
+
+
+- 提供 NovelAI 和 StableDiffusion 權重(weight)互相轉換的對話框
+- 從 `Prompt convert` 按鈕開啟
+- 預設為隱藏狀態
+- 啟用 `settings > D2 > Show prompt convert button` 後即可顯示
+
+
+
## :card_index_dividers: 示例工作流程
將圖像拖放到 ComfyUI 中可以重現工作流程。
@@ -511,6 +523,7 @@ ComfyUI Manager → Custom Node Manager → 搜索 `D2 Nodes` 並安裝
**2024.10.28**
+- Prompt convert: 新增 NovelAI 和 StableDiffusion 提示詞(prompt)互相轉換的對話框
- D2 Folder Image Queue: 修復生成圖片數量不一致的錯誤
**2024.10.26**
diff --git a/img/prompt_convert_dialog.png b/img/prompt_convert_dialog.png
new file mode 100644
index 0000000..5ca78ff
Binary files /dev/null and b/img/prompt_convert_dialog.png differ
diff --git a/pyproject.toml b/pyproject.toml
index 0d13918..3867a81 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "d2-nodes-comfyui"
description = "This is a collection of custom nodes that make ComfyUI slightly more convenient."
-version = "1.8.1"
+version = "1.9.1"
license = {file = "LICENSE"}
[project.urls]
diff --git a/web/D2_FloatContainer.js b/web/D2_FloatContainer.js
index df8b859..4b9353a 100644
--- a/web/D2_FloatContainer.js
+++ b/web/D2_FloatContainer.js
@@ -19,7 +19,6 @@ class D2_FloatContainer {
// 初期位置
const pos = this._getPosition(default_left, default_top);
- console.log("constructor", pos);
this.container.style.left = pos[0] + "px";
this.container.style.top = pos[1] + "px";
@@ -94,7 +93,6 @@ class D2_FloatContainer {
_getPosition(default_left, default_top) {
const cookieName = `${this.cookieNameBase}_pos`;
const cookieValue = getCookie(cookieName);
- console.log("_getPosition", cookieName, cookieValue);
return cookieValue !== undefined ? cookieValue : [default_left, default_top];
}
diff --git a/web/D2_PromptConverDialog.js b/web/D2_PromptConverDialog.js
new file mode 100644
index 0000000..ec3c8b2
--- /dev/null
+++ b/web/D2_PromptConverDialog.js
@@ -0,0 +1,246 @@
+import { app } from "../../scripts/app.js";
+import { D2_FloatContainer } from "./D2_FloatContainer.js";
+import { loadCssFile } from "./utils.js";
+
+
+/**
+ * プロンプト変換
+ */
+
+class D2_PromptConvert {
+ static RATE = 1.05;
+
+ /**
+ * StableDiffusionのweightをNAI方式に変換
+ */
+ static convertToNai(srcPrompt) {
+ const tempPrompt = srcPrompt.replace(/\n/g, '');
+ const convertedPrompt = tempPrompt.replace(/\([^)]+:\s*[0-9.]+\s*\)/g, D2_PromptConvert.$_convertToNai);
+ return convertedPrompt;
+ }
+
+ static $_convertToNai(prompt) {
+ 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);
+ 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) {
+ let tempPrompt = srcPrompt.replace(/\n/g, '');
+ let convertedPrompt = tempPrompt.replace(/[\[{]+[^\]}]+[\]}]+/g, D2_PromptConvert.$_convertToSd);
+ return convertedPrompt;
+ }
+
+ static $_convertToSd(prompt) {
+ 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 = "./extensions/D2-nodes-ComfyUI/css/D2_PromptConvertDialog.css";
+
+ container = undefined;
+
+ 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 to NAI", () => {
+ const prompt = sdPrompt.value;
+ const newPrompt = D2_PromptConvert.convertToNai(prompt);
+ naiPrompt.value = newPrompt;
+ });
+ content.appendChild(sdToNaiBtn);
+
+ // NAI > SD 変換ボタン
+ const naiToSdBtn = D2_PromptConvertDialog.createButton("NAI to SD", () => {
+ const prompt = naiPrompt.value;
+ const newPrompt = D2_PromptConvert.convertToSd(prompt);
+ sdPrompt.value = newPrompt;
+ });
+ content.appendChild(naiToSdBtn);
+
+ // SDクリップボード
+ const copySdBtn = D2_PromptConvertDialog.createButton("Send SD to Clipboard", () => {
+ sdPrompt.select();
+ document.execCommand("copy");
+ });
+ content.appendChild(copySdBtn);
+
+ // NAIクリップボード
+ const copyNaiBtn = D2_PromptConvertDialog.createButton("Send NAI to Clipboard", () => {
+ naiPrompt.select();
+ document.execCommand("copy");
+ });
+ 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);
+ }
+
+ /**
+ * プロンプトエリア作成
+ * @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;
+ }
+}
+
+///////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////
+
+/**
+ * ダイアログ表示ボタン
+ */
+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");
+ button.textContent = "Prompt convert";
+ this.floatContainer.addButton(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);
+ },
+ });
+})();
diff --git a/web/css/D2_PromptConvertDialog.css b/web/css/D2_PromptConvertDialog.css
new file mode 100644
index 0000000..2379c0a
--- /dev/null
+++ b/web/css/D2_PromptConvertDialog.css
@@ -0,0 +1,25 @@
+.d2-prompt-convert {
+ z-index: 1000;
+ width: 1000px;
+ max-width: 100%;
+}
+
+.d2-prompt-convert__content {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: .3rem 1rem;
+}
+
+/* .d2-prompt-convert__button-container {
+ gap: 0.25rem;
+} */
+
+.d2-prompt-convert__prompt-area{
+ height: 15rem;
+}
+
+
+.d2-prompt-convert__close-btn{
+ grid-column: span 2;
+ margin-top: 1rem;
+}
\ No newline at end of file