リリースノートを追加し、PromptRandomChoiceノードにchange_everyオプションを実装

This commit is contained in:
るみなー先生
2026-05-12 23:12:43 +09:00
parent 62cad94e19
commit 32831cbf17
3 changed files with 92 additions and 13 deletions
+35
View File
@@ -0,0 +1,35 @@
# v0.2.0
change_everyを追加したのじゃ!
`ComfyUI-PromptRandomChoice` は、プロンプト候補リストから実行のたびにランダムで1項目を選び、ポジティブプロンプトなどへ追加しやすい形で出力する ComfyUI カスタムノードです。
## 主な機能
- プロンプト候補から実行ごとにランダムで1項目を選択
- 候補は `|` または実際の改行で区切り
- 候補の前後にある空白や `,` を自動で trim
- 選ばれた候補を `,coffee shop,` のように前後カンマ付きで出力
- `change_every` に対応
- `1` なら毎回選び直し
- `3` なら同じ候補を3回返してから次を選択
- 実行中の選択結果をノードタイトルに表示
- 例: `Choice: coffee shop`
- 例: `Choice: lake (2/3)`
- 複数ノード設置に対応
- 背景用、時間帯用、天気用などを別々にランダム化可能
- 同じ候補を複数回書くことで、選ばれやすさを調整可能
- 例: `day|day|day|sunset|night`
## 注意
区切り文字は **実際の改行** または **`|`** です。
`,` は区切り文字ではありません。
ただし、各候補の前後に付いている `,` は自動で取り除きます。
詳しい使い方は README.md を参照してください。
## ライセンス
GPL-3.0
+50 -11
View File
@@ -1,4 +1,3 @@
import random
import re
import secrets
import time
@@ -22,9 +21,6 @@ coffee shop,"""
def _split_options(options_text: str):
text = str(options_text or "")
# Split by "|" or actual newlines only.
# Do not treat literal "\\n" text as a delimiter.
parts = re.split(r"\||\r?\n", text)
options = []
@@ -37,6 +33,12 @@ def _split_options(options_text: str):
class PromptRandomChoice:
def __init__(self):
self._current_choice = None
self._repeat_index = 0
self._last_options_key = None
self._last_change_every = None
@classmethod
def INPUT_TYPES(cls):
return {
@@ -48,6 +50,15 @@ class PromptRandomChoice:
"default": DEFAULT_OPTIONS_TEXT,
},
),
"change_every": (
"INT",
{
"default": 1,
"min": 1,
"max": 999999,
"step": 1,
},
),
},
}
@@ -58,30 +69,58 @@ class PromptRandomChoice:
@classmethod
def IS_CHANGED(cls, **kwargs):
# Force execution for every queued run so the random choice changes.
return time.time_ns()
def pick(self, options_text):
def _reset_state(self):
self._current_choice = None
self._repeat_index = 0
self._last_options_key = None
self._last_change_every = None
def _select_new_choice(self, options):
self._current_choice = secrets.choice(options)
self._repeat_index = 1
def pick(self, options_text, change_every):
options = _split_options(options_text)
change_every = max(1, int(change_every))
options_key = tuple(options)
if not options:
self._reset_state()
return {
"ui": {
"selected_text": [""],
"repeat_index": [0],
"change_every": [change_every],
},
"result": ("",),
}
# Do not use random.choice here.
# Some ComfyUI workflows/extensions may reset Python's global random state
# from the generation seed, which can make random.choice repeat the same
# index every queued run. secrets.choice uses OS entropy instead.
selected = secrets.choice(options)
state_changed = (
self._last_options_key != options_key
or self._last_change_every != change_every
or self._current_choice not in options
)
if state_changed or self._current_choice is None:
self._select_new_choice(options)
elif self._repeat_index < change_every:
self._repeat_index += 1
else:
self._select_new_choice(options)
self._last_options_key = options_key
self._last_change_every = change_every
selected = self._current_choice
prompt_text = f",{selected},"
return {
"ui": {
"selected_text": [selected],
"repeat_index": [self._repeat_index],
"change_every": [change_every],
},
"result": (prompt_text,),
}
+7 -2
View File
@@ -25,10 +25,15 @@ app.registerExtension({
originalOnExecuted?.apply(this, arguments);
const selected = message?.selected_text?.[0] ?? "";
if (selected) {
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)}`;
} else {
this.title = "Prompt Random Choice";
this.title = `Choice: ${shorten(selected)} (${repeatIndex}/${changeEvery})`;
}
this.setDirtyCanvas?.(true, true);