diff --git a/README.md b/README.md
index c2bf6ba..fbcd954 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@
- **区切りは `|` または実際の改行**
- `|` と改行の両方を区切りとして扱うので、混在していても動くぞ。
+ - 区切り文字が連続して出現した場合は、候補から取り除かれることに注意じゃ。
+ - 明示的に空白文字列を候補として返却させたい場合は、明示的に `()` と指定するのじゃ。
- **候補の前後の `,` と空白を自動で整理**
- `town,`
@@ -23,10 +25,6 @@
- `castle, fortress,`
のような書き方でも大丈夫じゃ。
-- **プロンプト結合向けの返り値**
- - 選ばれた候補の前後に `,` を付けて返すぞ。
- - 例: `coffee shop` → `,coffee shop,`
-
- **現在の選択結果をタイトル表示**
- 実行時にノードタイトルが `Choice: coffee shop` のように更新されるぞ。
- KSamplerのプレビューと見比べやすいのじゃ。
@@ -34,6 +32,12 @@
- **複数設置に対応**
- 背景用、時間帯用、天気用など、複数ノードを置いてそれぞれ別々に使えるぞ。
+- **ファイル名向けの返り値**
+ - ファイル名向けに安全化した文字列 `selected_text_safe` を出力するのじゃ。
+
+- **明示的な空候補 `()` に対応**
+ - `()` が選ばれた場合、プロンプト向け出力は空文字に、ファイル名向け出力は `empty` になるのじゃ。
+
## 導入方法
ComfyUIの `custom_nodes` ディレクトリで、以下のコマンドを打ち込むのじゃ!
@@ -47,7 +51,7 @@ git clone https://github.com/ruminar/ComfyUI-PromptRandomChoice.git
1. `Prompt Random Choice` ノードを置く
2. `options_text` に候補を入れる (入力例を参照)
3. 必要に応じて `change_every` を選ぶ (NEW)
-4. `prompt_text` を文字列結合ノードなどでポジティブプロンプトへ足す
+4. `selected_text` を `Join String Multi` などの文字列結合ノードへ繋ぎ、ポジティブプロンプトへ足す
5. キューを好きなだけ積む
@@ -95,6 +99,15 @@ flower field|
starry sky|
coffee shop|
```
+### 何も追加しない候補
+
+`()` は明示的な空候補として扱われます。
+
+```text
+()|(full body:0.9)
+```
+
+この例では、何も追加しない場合と、`(full body:0.9)` を追加する場合をランダムに切り替えられます。
### 選ばれやすさを調整したい場合
@@ -112,13 +125,20 @@ day|day|day|sunset|night
- `\n` という文字列は区切りとして扱わない
- 各候補の前後の空白と `,` を trim
- 空候補は無視
-- 有効な候補が1件も無い場合は空文字 `""` を返す
-- 候補があれば `,selected,` を返す
+- `()` は明示的な空候補として扱う
+- `change_every` が 1 なら毎回選び直す
+- `change_every` が 2 以上なら、その回数ぶん同じ候補を維持する
+- 実行時にタイトルへ `Choice: lake` や `Choice: (empty) (2/3)` のように表示する
## 出力
-- `prompt_text`
- - 例: `,coffee shop,` (結合しやすいように、前後をtrimしてから`,`を付与します)
+- `selected_text`
+ trim後の選択文字列です。
+ `()` が選ばれた場合は空文字 `""` になります。
+
+- `selected_text_safe`
+ ファイル名向けに安全化した出力です。
+ `selected_text` が空なら `empty` を返します。
@@ -154,8 +174,3 @@ GPL-3.0(ComfyUI本体の掟に従っておるぞ!)
## 宣伝画像
-
-※ 区切り文字は 改行 または `|` です。カンマは候補の前後に付いた場合だけ自動で取り除きます。
-
-(最初の版)
-
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 95784f0..9537bed 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,10 @@
-# v0.2.0
+## v0.3.0
-change_everyを追加したのじゃ!
+- 出力を整理
+ - `selected_text` : プロンプト向け出力
+ - `selected_text_safe` : ファイル名向け出力
+- 明示的な空候補 `()` に対応
+ - `()` が選ばれた場合、プロンプト向け出力は空文字になり、ファイル名向け出力は `empty` になります
`ComfyUI-PromptRandomChoice` は、プロンプト候補リストから実行のたびにランダムで1項目を選び、ポジティブプロンプトなどへ追加しやすい形で出力する ComfyUI カスタムノードです。
@@ -9,7 +13,8 @@ change_everyを追加したのじゃ!
- プロンプト候補から実行ごとにランダムで1項目を選択
- 候補は `|` または実際の改行で区切り
- 候補の前後にある空白や `,` を自動で trim
-- 選ばれた候補を `,coffee shop,` のように前後カンマ付きで出力
+- 選ばれた候補を `selected_text` として通常の文字列で出力
+- ファイル名向けに安全化した `selected_text_safe` を出力
- `change_every` に対応
- `1` なら毎回選び直し
- `3` なら同じ候補を3回返してから次を選択
@@ -20,6 +25,8 @@ change_everyを追加したのじゃ!
- 背景用、時間帯用、天気用などを別々にランダム化可能
- 同じ候補を複数回書くことで、選ばれやすさを調整可能
- 例: `day|day|day|sunset|night`
+- 明示的な空候補 `()` に対応
+ - `()` が選ばれた場合、プロンプト向け出力は空文字になり、ファイル名向け出力は `empty` になります
## 注意
diff --git a/nodes.py b/nodes.py
index 7bcb734..1e8df1f 100644
--- a/nodes.py
+++ b/nodes.py
@@ -26,12 +26,38 @@ def _split_options(options_text: str):
options = []
for part in parts:
item = str(part).strip(" \t\r\n,")
- if item:
- options.append(item)
+
+ # Empty parts caused by repeated delimiters are ignored.
+ if not item:
+ continue
+
+ # Explicit empty choice.
+ # Example: ()|(full body:0.9)
+ if item == "()":
+ options.append("")
+ continue
+
+ options.append(item)
return options
+def _safe_text(value: str):
+ text = str(value or "")
+
+ if not text:
+ return "empty"
+
+ text = text.replace("\\", "/").replace("/", "_")
+ text = re.sub(r'[<>:"|?*\x00-\x1f]', "_", text)
+ text = re.sub(r"[\s,]+", "_", text)
+ text = text.replace("(", "_").replace(")", "_")
+ text = re.sub(r"_+", "_", text)
+ text = text.strip().strip("._-")
+
+ return text or "empty"
+
+
class PromptRandomChoice:
def __init__(self):
self._current_choice = None
@@ -62,8 +88,8 @@ class PromptRandomChoice:
},
}
- RETURN_TYPES = ("STRING",)
- RETURN_NAMES = ("prompt_text",)
+ RETURN_TYPES = ("STRING", "STRING")
+ RETURN_NAMES = ("selected_text", "selected_text_safe")
FUNCTION = "pick"
CATEGORY = "utils/prompt"
@@ -90,11 +116,11 @@ class PromptRandomChoice:
self._reset_state()
return {
"ui": {
- "selected_text": [""],
+ "selected_text_title": ["(empty)"],
"repeat_index": [0],
"change_every": [change_every],
},
- "result": ("",),
+ "result": ("", "empty"),
}
state_changed = (
@@ -113,16 +139,17 @@ class PromptRandomChoice:
self._last_options_key = options_key
self._last_change_every = change_every
- selected = self._current_choice
- prompt_text = f",{selected},"
+ selected_text = str(self._current_choice or "")
+ selected_text_safe = _safe_text(selected_text)
+ selected_text_title = selected_text if selected_text else "(empty)"
return {
"ui": {
- "selected_text": [selected],
+ "selected_text_title": [selected_text_title],
"repeat_index": [self._repeat_index],
"change_every": [change_every],
},
- "result": (prompt_text,),
+ "result": (selected_text, selected_text_safe),
}
diff --git a/web/prompt_random_choice.js b/web/prompt_random_choice.js
index 2723619..e44f2ec 100644
--- a/web/prompt_random_choice.js
+++ b/web/prompt_random_choice.js
@@ -24,16 +24,14 @@ app.registerExtension({
nodeType.prototype.onExecuted = function (message) {
originalOnExecuted?.apply(this, arguments);
- const selected = message?.selected_text?.[0] ?? "";
+ const selectedTitle = message?.selected_text_title?.[0] ?? "(empty)";
const repeatIndex = Number(message?.repeat_index?.[0] ?? 0);
const changeEvery = Number(message?.change_every?.[0] ?? 1);
- if (!selected) {
- this.title = "Prompt Random Choice";
- } else if (changeEvery <= 1) {
- this.title = `Choice: ${shorten(selected)}`;
+ if (changeEvery <= 1) {
+ this.title = `Choice: ${shorten(selectedTitle)}`;
} else {
- this.title = `Choice: ${shorten(selected)} (${repeatIndex}/${changeEvery})`;
+ this.title = `Choice: ${shorten(selectedTitle)} (${repeatIndex}/${changeEvery})`;
}
this.setDirtyCanvas?.(true, true);