新規作成

This commit is contained in:
るみなー先生
2026-05-12 21:25:19 +09:00
commit d110477e9d
4 changed files with 263 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
# ComfyUI-PromptRandomChoice
実行のたびに「プロンプトガチャ」を爆速で回す、超軽量なランダム選択ノードじゃ!
「背景や天気を適当に変えて、一晩中回しておきたいのう……」
そんなおぬしの願いを、実行時間 0.010s(注:作者環境で速いとき)という瞬速の魔法で叶えて進ぜよう!
背景・時間帯・天気などを別々にランダム化したい時に便利じゃ。
フロー内に複数個置いても、それぞれ独立して動くぞ。
## 特徴
- **実行ごとにランダム選択**
- キューを雑に100件積んでも、毎回候補を選び直すぞ。
- **区切りは `|` または実際の改行**
- `|` と改行の両方を区切りとして扱うので、混在していても動くぞ。
- **候補の前後の `,` と空白を自動で整理**
- `town,`
- `coffee shop,`
- `castle, fortress,`
のような書き方でも大丈夫じゃ。
- **プロンプト結合向けの返り値**
- 選ばれた候補の前後に `,` を付けて返すぞ。
- 例: `coffee shop` → `,coffee shop,`
- **現在の選択結果をタイトル表示**
- 実行時にノードタイトルが `Choice: coffee shop` のように更新されるぞ。
- KSamplerのプレビューと見比べやすいのじゃ。
- **複数設置に対応**
- 背景用、時間帯用、天気用など、複数ノードを置いてそれぞれ別々に使えるぞ。
## 導入方法
ComfyUIの `custom_nodes` ディレクトリで、以下のコマンドを打ち込むのじゃ!
```bash
git clone https://github.com/ruminar/ComfyUI-PromptRandomChoice.git
```
## 使い方
1. `Prompt Random Choice` ノードを置く
2. `options_text` に候補を入れる
3. `prompt_text` を文字列結合ノードなどでポジティブプロンプトへ足す
4. キューを好きなだけ積む
## 入力例
### 改行区切り (改行前の`,`は任意です)
```text
town,
girl's room,
park,
lake,
flower garden,
castle, fortress,
forest,
grasslands,
sea,
snowy landscape,
mountain,
flower field,
starry sky,
coffee shop,
```
### `|` 区切り
```text
town|
girl's room|
park|
lake|
flower garden|
castle, fortress|
forest|
grasslands|
sea|
snowy landscape|
mountain|
flower field|
starry sky|
coffee shop|
```
### 選ばれやすさを調整したい場合
同じ候補を複数回書けば、そのぶん選ばれやすくなります。
```text
day|day|day|sunset|night
```
昼を多めに出したい、たまに夕方や夜も混ぜたい、という時に便利じゃ。
## 仕様
- `|` または実際の改行で分割
- `\n` という文字列は区切りとして扱わない
- 各候補の前後の空白と `,` を trim
- 空候補は無視
- 候補が空なら空文字 `""` を返す
- 候補があれば `,selected,` を返す
## 出力
- `prompt_text`
- 例: `,coffee shop,`
## ライセンス
GPL-3.0(ComfyUI本体の掟に従っておるぞ!)
## 宣伝画像
+9
View File
@@ -0,0 +1,9 @@
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
WEB_DIRECTORY = "./web"
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"WEB_DIRECTORY",
]
+96
View File
@@ -0,0 +1,96 @@
import random
import re
import secrets
import time
DEFAULT_OPTIONS_TEXT = """town,
girl's room,
park,
lake,
flower garden,
castle, fortress,
forest,
grasslands,
sea,
snowy landscape,
mountain,
flower field,
starry sky,
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 = []
for part in parts:
item = str(part).strip(" \t\r\n,")
if item:
options.append(item)
return options
class PromptRandomChoice:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"options_text": (
"STRING",
{
"multiline": True,
"default": DEFAULT_OPTIONS_TEXT,
},
),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt_text",)
FUNCTION = "pick"
CATEGORY = "utils/prompt"
@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):
options = _split_options(options_text)
if not options:
return {
"ui": {
"selected_text": [""],
},
"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)
prompt_text = f",{selected},"
return {
"ui": {
"selected_text": [selected],
},
"result": (prompt_text,),
}
NODE_CLASS_MAPPINGS = {
"PromptRandomChoice": PromptRandomChoice,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PromptRandomChoice": "Prompt Random Choice",
}
+38
View File
@@ -0,0 +1,38 @@
import { app } from "../../scripts/app.js";
const EXTENSION_NAME = "ruminar.PromptRandomChoice";
const NODE_NAME = "PromptRandomChoice";
function shorten(text, maxLength = 40) {
const value = String(text ?? "");
if (value.length <= maxLength) {
return value;
}
return value.slice(0, maxLength - 3) + "...";
}
app.registerExtension({
name: EXTENSION_NAME,
beforeRegisterNodeDef(nodeType, nodeData) {
if (String(nodeData?.name ?? "") !== NODE_NAME) {
return;
}
const originalOnExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
originalOnExecuted?.apply(this, arguments);
const selected = message?.selected_text?.[0] ?? "";
if (selected) {
this.title = `Choice: ${shorten(selected)}`;
} else {
this.title = "Prompt Random Choice";
}
this.setDirtyCanvas?.(true, true);
app.graph?.setDirtyCanvas?.(true, true);
};
},
});