48 Commits
Author SHA1 Message Date
NakamuraShippo fc3baafa9c Update README_jp.md to reflect repository status
This repository has been discontinued and integrated into ComfyUI-NS-Util.
2026-03-09 20:29:55 +09:00
NakamuraShippo f69497e46b Update README.md 2026-03-09 20:29:27 +09:00
NakamuraShippo 49b75c9bbb Revise README with repository update notice
Updated repository status and added line breaks.
2026-03-09 20:25:53 +09:00
NakamuraShippo b81f1b6459 Update README to reflect repository status
Added notice about repository merge into ComfyUI-NS-Util.
2026-03-09 20:23:16 +09:00
NakamuraShippo 670b33ae2f Merge pull request #10 from NaokiNakamichi/fix-node-issues
Fix node copy linking issue and async save error

プルリクエストありがとうございます!
手を広げて色々やってるせいかメンテナンスが出来ない状況が続いてるのでとてもありがたいです😊
2025-12-03 12:34:56 +09:00
Naoki Nakamichi b00debed9e Fix node copy linking issue and async save error 2025-11-27 21:26:37 +09:00
NakamuraShippo 79d7de96f4 v2.0.3 bug fix 2025-11-05 23:27:10 +09:00
NakamuraShippo 6262b95cfb Update .gitignore 2025-08-01 13:07:09 +09:00
NakamuraShippo 67c72d6797 Update README_jp.md 2025-06-21 17:50:06 +09:00
NakamuraShippo 9dbd32d5b6 v2.0.2 bug fix 2025-06-01 17:44:24 +09:00
NakamuraShippo f3836a2cd5 v2.0.1 bug fix 2025-06-01 17:08:13 +09:00
NakamuraShippo d6258d9a1f Update README.md 2025-06-01 11:48:52 +09:00
NakamuraShippo 0c40477c3d Update README_jp.md 2025-06-01 11:48:17 +09:00
NakamuraShippo cbe8d620e7 Update README.md 2025-06-01 11:47:36 +09:00
NakamuraShippo 4d23e0d2ed Update README.md 2025-06-01 11:46:23 +09:00
NakamuraShippo 32dd1e3852 Update README.md 2025-06-01 11:46:11 +09:00
NakamuraShippo f8ab9697cd Update README.md 2025-06-01 11:45:54 +09:00
NakamuraShippo 580d24eaa3 v2.0.0 2025-06-01 00:55:29 +09:00
NakamuraShippo 12ddfcb2da Update README.md 2025-05-31 20:34:15 +09:00
NakamuraShippo d9d9027680 Update pyproject.toml 2024-11-03 11:25:08 +09:00
NakamuraShippo 32d8e84995 Update publish_action.yml 2024-11-03 11:24:42 +09:00
NakamuraShippo a3a49d3699 Update pyproject.toml 2024-11-03 10:44:26 +09:00
NakamuraShippo 5413f69be1 Rename publish.yml to publish_action.yml 2024-11-03 10:38:22 +09:00
NakamuraShippo 322dc6abf1 Update pyproject.toml 2024-11-03 10:14:54 +09:00
NakamuraShippo be63c40b5e Update pyproject.toml 2024-11-02 22:24:43 +09:00
NakamuraShippo b58a96009d Update README_jp.md 2024-09-07 15:31:51 +09:00
NakamuraShippo 714bdd2c5c Update README_jp.md 2024-09-07 15:21:43 +09:00
NakamuraShippo 4b4143a334 Update README.md 2024-09-07 15:10:51 +09:00
NakamuraShippo 2303daa3f4 Update README_jp.md 2024-09-07 15:10:46 +09:00
NakamuraShippo 72ddf3dcff Update ComfyUI_PromptList.py 2024-09-07 00:07:07 +09:00
NakamuraShippo c12fdc9834 Update README_jp.md 2024-09-06 23:15:58 +09:00
NakamuraShippo aeeb1926f6 Update README.md 2024-09-06 23:15:34 +09:00
NakamuraShippo 0e2d7dd1ff Update README_jp.md 2024-09-06 23:10:37 +09:00
NakamuraShippo 48ae8b5aaf Update pyproject.toml 2024-09-06 23:03:44 +09:00
NakamuraShippo 936d131b49 バグ修正:アップデートの際にprompts.yamlを上書きしてしまうのを修正しました。
また、prompts.yamlをcomfyUI\user\PromptList\prompts.yamlで扱うように変更
2024-09-06 23:03:01 +09:00
NakamuraShippo fd0ab4c64d positive negativeが空でも動作するように変更 2024-09-06 22:29:21 +09:00
NakamuraShippo 4832ea3f89 Update README_jp.md 2024-09-06 13:42:52 +09:00
NakamuraShippo d46577aef9 Update README.md 2024-09-06 13:42:25 +09:00
NakamuraShippo e5a2e08e35 Update README_jp.md 2024-09-06 13:40:58 +09:00
NakamuraShippo eb3ec610e9 Add files via upload 2024-09-06 13:39:28 +09:00
NakamuraShippo ed33237a43 Update README.md 2024-09-06 12:39:10 +09:00
NakamuraShippo c6bf627620 Update README.md 2024-09-06 11:07:59 +09:00
NakamuraShippo c0cf34d8c3 Update README.md 2024-09-06 11:07:06 +09:00
NakamuraShippo babe3b7894 Update README.md 2024-09-06 10:56:25 +09:00
NakamuraShippo 7d9de1460c Update README_jp.md 2024-09-06 10:55:01 +09:00
NakamuraShippo d734ae42fc Update README.md 2024-09-06 10:50:43 +09:00
NakamuraShippo 4df8bd6ff5 Update and rename README_en.md to README.md 2024-09-06 10:50:19 +09:00
NakamuraShippo 3c42f923e4 Merge pull request #4 from NakamuraShippo/dev
1.2.0
2024-09-06 10:47:28 +09:00
19 changed files with 814 additions and 332 deletions
@@ -4,21 +4,16 @@ on:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
# if this is a forked repository. Skipping the workflow.
if: github.event.repository.fork == false
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+6
View File
@@ -160,3 +160,9 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#PromptList
yaml/
#CODEX
AGENTS.md
-95
View File
@@ -1,95 +0,0 @@
import yaml
import os
import folder_paths
from collections import OrderedDict
# カスタムYAMLローダーとダンパーを定義
class OrderedLoader(yaml.SafeLoader):
pass
class OrderedDumper(yaml.SafeDumper):
pass
def dict_constructor(loader, node):
return OrderedDict(loader.construct_pairs(node))
OrderedLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, dict_constructor)
def dict_representer(dumper, data):
return dumper.represent_mapping(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items())
OrderedDumper.add_representer(OrderedDict, dict_representer)
class ComfyUI_PromptList:
def __init__(self):
self.yaml_path = os.path.join(folder_paths.base_path, "custom_nodes", "ComfyUI-PromptList", "prompts.yaml")
self.data = self.load_yaml()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"selection": (cls.get_prompt_list(),),
"Prompt Name": ("STRING", {"multiline": True}),
"Positive Prompt": ("STRING", {"multiline": True}),
"Negative Prompt": ("STRING", {"multiline": True}),
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("positive", "negative")
FUNCTION = "process"
CATEGORY = "prompt"
def load_yaml(self):
if os.path.exists(self.yaml_path):
with open(self.yaml_path, 'r', encoding='utf-8') as file:
return yaml.load(file, Loader=OrderedLoader) or OrderedDict()
return OrderedDict()
def save_yaml(self):
with open(self.yaml_path, 'w', encoding='utf-8') as file:
yaml.dump(self.data, file, Dumper=OrderedDumper, allow_unicode=True)
@classmethod
def get_prompt_list(cls):
instance = cls()
return list(instance.data.keys())
def process(self, selection, **kwargs):
prompt_name = kwargs.get("Prompt Name", "")
positive_prompt = kwargs.get("Positive Prompt", "")
negative_prompt = kwargs.get("Negative Prompt", "")
if prompt_name and positive_prompt and negative_prompt:
# Update or add new prompt while preserving order
if prompt_name in self.data:
self.data[prompt_name]["positive"] = positive_prompt
self.data[prompt_name]["negative"] = negative_prompt
else:
self.data[prompt_name] = OrderedDict([
("positive", positive_prompt),
("negative", negative_prompt)
])
self.save_yaml()
return (positive_prompt, negative_prompt)
elif selection:
# Return existing prompt from YAML
prompt = self.data.get(selection, {})
return (prompt.get("positive", ""), prompt.get("negative", ""))
# Default return if no selection or custom input
return ("", "")
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
return float("NaN")
# NODE_CLASS_MAPPINGS と NODE_DISPLAY_NAME_MAPPINGS の定義
NODE_CLASS_MAPPINGS = {
"ComfyUI-PromptList": ComfyUI_PromptList
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUI-PromptList": "Prompt List"
}
+74
View File
@@ -0,0 +1,74 @@
# ComfyUI-NS-PromptList
[日本語README](https://github.com/NakamuraShippo/ComfyUI-NS-PromptList/blob/main/README_jp.md)
This repository is no longer being updated and has been merged into [ComfyUI-NS-Util](https://github.com/NakamuraShippo/ComfyUI-NS-Util).
<br>
///////////////////////////////////////////////////////////////////////////////////////////////////////
<br>
ComfyUI-NS-PromptList is a simple prompt management node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that outputs prompts recorded in .yaml.
## Features
- Load prompts from prompts.yaml and output positive and negative prompts in String format
- Write new prompts to yaml
- Edit prompts registered in yaml
## Installation
If you have [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) installed:
1. Click Manager -> Install via Git URL from the main menu
2. Paste the URL in the text box that appears at the top of the window and press OK
https://github.com/NakamuraShippo/ComfyUI-NS-PromptList
3. Once the installation is complete, restart ComfyUI
If you don't have [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) installed:
1. Navigate to ComfyUI's custom nodes directory (usually ComfyUI/custom_nodes/)
2. Clone this repository:
`git clone https://github.com/NakamuraShippo/ComfyUI-NS-PromptList`
3. Restart ComfyUI
4. In ComfyUI\venv\Scripts, Shift+Right-click → Open in terminal -> type "activate"
```
pip install pyyaml watchdog filelock
```
## Usage
Node location:
Add Node -> NS -> NS Prompt List
Node widgets:
- select_yaml: List of yaml files in ComfyUI-Create\custom_nodes\ComfyUI-NS-PromptList\yaml, loads the selected yaml
- select: Select from a list of title keys in the yaml selected by select_yaml to load the title and string
- title and text area: Content of the loaded title and string information
How to register prompts:
1. Enter an identifying title in the title field
2. Enter any string in the text area
3. Generate to write to yaml
How to edit prompts:
1. Enter the name of the prompt you want to edit in the title field
2. Enter any string in the text area
3. Generate
4. If the same title name exists in the specified yaml, the string will be overwritten
If you accidentally edit:
Press CTRL + Z to redo and restore the string, then generate again to overwrite
## Update History
2025/05/31 2.0.0 It has been completely redesigned and is now all on ComfyUI.
2024/09/06 1.2.0 Modified node input field to multiline. Published editing spreadsheet.
2024/08/24 1.0.0 Initial release as it's working properly
## License
This project is released under the MIT License. For details, see the [LICENSE.txt](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/License.txt) file.
You are free to use, modify, and distribute this software for personal and commercial purposes as long as you include the original copyright notice and disclaimer.
## Other
Bug reports and feature requests are welcome through any means of contact.
Pull requests are also welcome.
## Contact
[NakamuraShippo](https://lit.link/admin/creator)
-123
View File
@@ -1,123 +0,0 @@
# ComfyUI-PromptList
ComfyUI-PromptList is a simple prompt management node for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that outputs prompts recorded in prompts.yaml.
## Features
- Loads prompts from prompts.yaml and outputs positive and negative prompts in String format.
- Writes new prompts to the yaml file.
- Modifies existing prompts registered in the yaml file.
## Installation
If you have [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) installed:
1. Click Manager -> Install via Git URL in the main menu.
2. Paste the URL into the text box that appears at the top of the window and press OK:
https://github.com/NakamuraShippo/ComfyUI-PromptList
3. After installation is complete, restart ComfyUI.
If you don't have [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) installed:
1. Navigate to the ComfyUI custom node directory. (Usually ComfyUI/custom_nodes/)
2. Clone this repository:
`git clone https://github.com/NakamuraShippo/ComfyUI-PromptList`
3. Restart ComfyUI.
## Usage
![node](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/node1.png)
Right-click on the workspace -> Add node -> prompt -> Prompt List to add the node.
The node has the following inputs:
selection: A list of prompts registered in prompts.yaml. Outputs the prompt for the selected item.
If the following items are filled in, the prompt from the selection will not be output, and the entered prompt will be output instead:
Prompt Name: New prompt name or name of the prompt to update
Positive Prompt: Content of the positive prompt
Negative Prompt: Content of the negative prompt
The entered prompt name and prompts will be saved in prompts.yaml.
If you change a prompt using the same Prompt Name, it will overwrite the prompt in prompts.yaml.
<<<<<<< HEAD
### We have prepared a [spreadsheet for editing prompts.yaml](https://docs.google.com/spreadsheets/d/1TxATrMXC1X1iSBRD4yFQZoOOwwNVz6irTSpnyv2T1ec/edit?usp=sharing).
#### Preparation before using the spreadsheet
1. File -> Make a copy -> Edit to your preferred file name and click "Make a copy"
2. After a while, "Export" will appear to the right of Help. Click Export -> Export as YAML
3. Authentication required window -> OK
4. Select your Google account and log in (if a warning appears: Advanced -> Go to prompt.yaml export (unsafe) -> Allow)
=======
### We have prepared a [spreadsheet for editing prompts.yaml](https://docs.google.com/spreadsheets/d/1f4-kQ2YnETfa_peiRiskK3abuR4LqCTHxDwrpVqCYpY/edit?usp=sharing).
#### Preparation before using the spreadsheet
1. File -> Make a copy -> Edit to your preferred file name and click "Create copy"
2. After a while, "Export" will appear to the right of Help. Click Export -> Export as YAML
3. Authentication required window -> OK
4. Select your Google account and log in (if a warning appears: Show details -> Move to unsafe page -> Allow)
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
#### How to fill in the spreadsheet
![spreadsheets](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/spreadsheets.png)
"prompt name" cell = prompt name
Cell to the right of "positive" = positive prompt
Cell to the right of "negative" = negative prompt
Enter each of these. (The white cells in columns A and C are the input cells)
If you want to increase the number of items to register, please use autofill.
![autofill](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/autofill.gif)
*Note*
Do not edit cells filled with gray.
#### Exporting YAML
1. After a while, "Export" will appear to the right of Help. Click Export/Import -> Export -> Export complete
2. prompts.yaml will be output to your Google Drive.
#### Importing YAML
1. Place prompts.yaml in the same folder as the spreadsheet
2. Click Export/Import -> Import -> Import complete
#### Recommended usage
If you make the following settings before exporting the yaml, you can save the trouble of moving files:
1. Install Google Drive
2. Set up a symbolic link to prompts.yaml in the folder of this custom node
3. When prompts.yaml in Google Drive is updated, the file on your desktop will also be synchronized in about 10 seconds.
4. Success!
##### About symbolic links (for those who don't know)
It's similar to a shortcut, but the crucial difference is that it can behave as if it has a real existence.
Open the command prompt and execute the following command, replacing the links with the appropriate locations for your environment:
~~~
Windows
mklink path_to_prompts.yaml_in_this_custom_node path_to_prompts.yaml_file_in_GoogleDrive
Mac/Linux
ln -s path_to_prompts.yaml_file_in_GoogleDrive path_to_prompts.yaml_in_this_custom_node
Example
mklink E:\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-PromptList\prompts.yaml Y:\My Drive\ComfyUI-PromptList\prompts.yaml
~~~
For Windows, if you introduce an official app called PowerToys, you can easily set it up with a right-click.
Give it a try!
## Notes
The prompt list is saved in the prompts.yaml file.
If you edit, add new prompts, or make changes, please press Refresh in the ComfyUI main menu to reflect the changes.
## Update History
<<<<<<< HEAD
2024/09/06 1.2.0 Node input field is now multiline. Edit spreadsheet is now available.
2024/08/24 1.0.0 Initial public release as it's working for now
=======
2024/09/05 Made it possible to manage prompts.yaml in any folder. Editing spreadsheet also published.
2024/08/24 Initial public release as it's working for now
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
## License
This project is released under the MIT License. For details, please refer to the [LICENSE.txt](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/License.txt) file.
You are free to use, modify, and distribute this software for personal and commercial purposes as long as you include the original copyright notice and disclaimer.
## Other
Bug reports or feature requests are welcome through any contactable means.
Pull requests are also welcome.
## Contact
[NakamuraShippo](https://lit.link/admin/creator)
+36 -81
View File
@@ -1,5 +1,13 @@
# ComfyUI-PromptList
ComfyUI-PromptListは、[ComfyUI](https://github.com/comfyanonymous/ComfyUI)用のprompts.yamlに記録したプロンプトを出力するシンプルなプロンプト管理ノードです。
# ComfyUI-NS-PromptList
このリポジトリは更新終了し、[ComfyUI-NS-Util](https://github.com/NakamuraShippo/ComfyUI-NS-Util)に統合しました。
<br>
///////////////////////////////////////////////////////////////////////////////////////////////////////
<br>
ComfyUI-PromptListは、[ComfyUI](https://github.com/comfyanonymous/ComfyUI)用のyamlに記録したプロンプトを出力するシンプルなプロンプト管理ノードです。
## 機能
@@ -11,102 +19,49 @@ ComfyUI-PromptListは、[ComfyUI](https://github.com/comfyanonymous/ComfyUI)用
[ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager)をインストールしている場合
1. メインメニューのManager -> Install via Git URLの順にクリックする
2. ウインドウ上部に出てくるテキストボックスにURLを貼り付けてOKを押す
https://github.com/NakamuraShippo/ComfyUI-PromptList
https://github.com/NakamuraShippo/ComfyUI-NS-PromptList
3. インストールが完了したら、ComfyUIを再起動
[ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager)をインストールしていない場合
1. ComfyUIのカスタムノードディレクトリに移動します。(通常は ComfyUI/custom_nodes/)
2. このリポジトリをクローンします。
`git clone https://github.com/NakamuraShippo/ComfyUI-PromptList`
`git clone https://github.com/NakamuraShippo/ComfyUI-NS-PromptList`
3. ComfyUIを再起動します。
4. ComfyUI\venv\ScriptsでShift+右クリック→ターミナルで開く -> activateと入力
~~~
pip install pyyaml watchdog filelock
~~~
## 使用方法
![node](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/node1.png)
ワークスペース上で右クリック -> Add node-> prompt -> Prompt Listでノードを追加します。
ノードには以下の入力があります:
selection: prompts.yamlに登録されているプロンプトリスト、選択した項目のプロンプトを出力します。
以下の項目に入力されている場合はselectionのプロンプトは出力されずに入力したプロンプトが出力されます。
Prompt Name: 新しいプロンプト名または更新するプロンプト名
Positive Prompt: ポジティブプロンプトの内容
Negative Prompt: ネガティブプロンプトの内容
入力したプロンプト名とプロンプトはprompts.yamlに保存されます。
また、同名のPrompt Nameを使用してプロンプトを変更した場合はprompts.yaml内のプロンプトを上書きします。
### [prompts.yaml編集用スプレッドシート](https://docs.google.com/spreadsheets/d/1f4-kQ2YnETfa_peiRiskK3abuR4LqCTHxDwrpVqCYpY/edit?usp=sharing)を用意しました。
ノードの場所
Add Node -> NS -> NS Prompt List
#### スプレッドシートを利用する前準備
1. ファイル -> コピー作成 -> 好きなファイル名に編集し「コピーを作成」
2. しばらく待つとヘルプの右側に「エクスポート」が現れるので、エクスポート -> YAMLとしてエクスポート
3. 認証が必要ですウインドウ -> OK
4. googleアカウントを選択してログイン(警告が出た場合:詳細を表示 -> 安全ではないページに移動 -> 許可)
ノードのウィジェット
- select_yaml : ComfyUI-Create\custom_nodes\ComfyUI-NS-PromptList\yaml にあるyamlのリスト、選んだyamlをロードします
- select : select_yamlで選択したyaml内のtitleキーの一覧を選択すると、titleと文字列を読み込みます
- title and text area : ロードされたtitleと文字列情報の内容です
#### スプレッドシートの記述方法
![spreadsheets](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/spreadsheets.png)
「prompt name」のセル = prompt name
「positive」の右隣のセル = positive prompt
「negative」の右隣のセル = negative prompt
をそれぞれ入力します。(A列とC列の白色セルが入力可能なセルとなっています)
登録する項目を増やしたい時はオートフィルをご利用ください。
![autofill](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/images/autofill.gif)
プロンプトの登録方法
1. titleに識別用の見出しを入力します
2. テキストエリアに任意の文字列を入力します
3. 生成するとyamlへ書き込まれます
*注意*
グレーで塗りつぶされたセルは編集しないでください。
プロンプトの編集方法
1. titleに編集したいプロンプトの名前を入力
2. テキストエリアに任意の文字列を入力
3. 生成
4. 指定したyaml内に同じtitle名があれば、文字列を上書きします
#### YAMLのエクスポート
<<<<<<< HEAD
1. しばらく待つとヘルプの右側に「エクスポート/インポート」が現れるので、エクスポート/インポート -> エクスポート -> エクスポート完了
=======
1. しばらく待つとヘルプの右側に「エクスポート」が現れるので、エクスポート/インポート -> エクスポート -> エクスポート完了
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
2. ご自身のgoogleドライブにprompts.yamlが出力されます。
#### YAMLのインポート
1. スプレッドシートと同じフォルダ内にprompts.yamlを配置
2. エクスポート/インポート -> インポート -> インポート完了
#### 推奨する使い方
以下の設定をした上でyamlのエクスポートを行うとファイルの移動の手間がなく楽が出来ますよ。
1. googleドライブをインストール
2. prompts.yamlを当カスタムノードのフォルダ内にシンボリックリンクを設定する
3. googleドライブ内のprompts.yamlが更新されると10秒前後でデスクトップ内のファイルも同期されます。
4. やったぜ。
##### シンボリックリンクについて(知らない人向け)
ショートカットのようなものですが、決定的な違いは実体があるかのように振舞う事ができることです。
コマンドプロンプト(ターミナル)を開き、リンクを皆さんの環境に合った箇所に書き換えて実行してください。
~~~
windows
mklink 当カスタムノードのprompts.yamlのパス GoogleDrive内のprompts.yamlファイルのパス
Mac/Linux
ln -s GoogleDrive内のprompts.yamlファイルのパス 当カスタムノードのprompts.yamlのパス
例
mklink E:\ComfyUI_windows_portable\ComfyUI\custom_nodes\ComfyUI-PromptList\prompts.yaml Y:\マイドライブ\ComfyUI-PromptList\prompts.yaml
~~~
windowsの場合はpowertoysという公式のアプリを導入すると右クリックで簡単に設定できたりもします。
大変便利なので探してインストールしてみてね。
## 注意事項
プロンプトリストはprompts.yamlファイルに保存されます。
編集したり新しいプロンプトを追加または編集を行った場合、変更を反映させる為にComfyUIのメインメニューにあるRefreshを押してください。
誤って編集した場合
CTRL + Z でredoすると文字列が復元されるので、再度生成して上書きしてください
## アップデート履歴
<<<<<<< HEAD
2024/09/06 1.2.0 ノード入力欄をmultilineに修正。編集用スプレッドシートを公開。
2025/05/31 2.0.0 根本的に作り直しました。ComfyUI上で完結するようにしました。
2024/09/06 1.2.0 ノード入力欄をmultilineに修正。編集用スプレッドシートを公開。
2024/08/24 1.0.0 とりあえず動いてるので公開
=======
2024/09/05 prompts.yamlを任意のフォルダで管理できるようにしました。編集用スプレッドシートも公開。
2024/08/24 とりあえず動いてるので公開
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
## ライセンス
このプロジェクトは MIT ライセンスに基づいてリリースされています。詳細については、[LICENSE.txt](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/License.txt)ファイルを参照してください。
このプロジェクトは MIT ライセンスに基づいてリリースされています。詳細については、[LICENSE.txt](https://github.com/NakamuraShippo/ComfyUI-PromptList/blob/main/License.txt)ファイルを参照してください。
元の著作権表示と免責事項を含める限りは、このソフトウェアを個人的および商業的な目的で自由に使用、変更、配布できます。
## その他
+5 -16
View File
@@ -1,21 +1,10 @@
"""
@author: NakamuraShippo
@title: ComfyUI-PromptList
@description: Custom node to manage prompts in YAML format.
ComfyUI-NS-PromptList
A ComfyUI custom node for managing prompts with YAML files
"""
from .ComfyUI_PromptList import ComfyUI_PromptList
from .prompt_list_node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
NODE_CLASS_MAPPINGS = {
"ComfyUI-PromptList": ComfyUI_PromptList
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUI-PromptList": "Prompt List"
}
WEB_DIRECTORY = "./web"
print("ComfyUI-PromptList initialized")
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
WEB_DIRECTORY = "./web"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

+327
View File
@@ -0,0 +1,327 @@
import os
import yaml
import json
import asyncio
import threading
import shutil
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple, Optional, Any
import tempfile
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler, FileSystemEvent
from server import PromptServer
class YAMLFileHandler(FileSystemEventHandler):
"""Handles file system events for YAML files"""
def __init__(self, node_instance):
self.node_instance = node_instance
def on_modified(self, event: FileSystemEvent):
if not event.is_directory and event.src_path.endswith('.yaml'):
self.node_instance.refresh_enums()
def on_created(self, event: FileSystemEvent):
if not event.is_directory and event.src_path.endswith('.yaml'):
self.node_instance.refresh_enums()
def on_deleted(self, event: FileSystemEvent):
if not event.is_directory and event.src_path.endswith('.yaml'):
self.node_instance.refresh_enums()
class NS_PromptList:
"""NS-PromptList Node for ComfyUI"""
def __init__(self):
self.yaml_dir = Path(__file__).parent / "yaml"
self.yaml_dir.mkdir(exist_ok=True)
self.write_lock = threading.Lock()
self.observer = None
self.file_handler = YAMLFileHandler(self)
# Store server instance
self.server = PromptServer.instance
# Start watchdog observer
self._start_watchdog()
# Register socket handlers
self._register_socket_handlers()
# Initial enum refresh
self.refresh_enums()
@classmethod
def INPUT_TYPES(cls):
# Always get fresh YAML list when INPUT_TYPES is called
yaml_dir = Path(__file__).parent / "yaml"
yaml_dir.mkdir(exist_ok=True)
yaml_files = [f.name for f in yaml_dir.glob("*.yaml")]
if not yaml_files:
# Create default yaml if none exist
default_yaml = yaml_dir / "default.yaml"
default_yaml.write_text("example:\n prompt: 'Enter your prompt here'\n")
yaml_files = ["default.yaml"]
# Get all possible titles from all YAML files for validation
all_titles = set([""]) # Always include empty string
for yaml_file in yaml_files:
yaml_path = yaml_dir / yaml_file
try:
with open(yaml_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
all_titles.update(data.keys())
except:
pass
return {
"required": {
"select_yaml": (sorted(yaml_files), {"default": yaml_files[0] if yaml_files else ""}),
"select": (sorted(list(all_titles)), {"default": ""}), # Include all possible titles
"title": ("STRING", {"default": "", "multiline": False}),
"prompt": ("STRING", {"default": "", "multiline": True}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("STRING",)
FUNCTION = "run"
CATEGORY = "NS"
@classmethod
def _get_instance(cls):
"""Get or create singleton instance"""
if not hasattr(cls, '_instance'):
cls._instance = cls()
return cls._instance
def _get_yaml_files(self) -> List[str]:
"""Get list of YAML files in yaml directory"""
if not self.yaml_dir.exists():
return ["default.yaml"]
yaml_files = [f.name for f in self.yaml_dir.glob("*.yaml")]
if not yaml_files:
# Create default yaml if none exist
default_yaml = self.yaml_dir / "default.yaml"
default_yaml.write_text("example:\n prompt: 'Enter your prompt here'\n")
yaml_files = ["default.yaml"]
return sorted(yaml_files)
def _get_titles_from_yaml(self, yaml_file: str) -> List[str]:
"""Get titles from a YAML file"""
yaml_path = self.yaml_dir / yaml_file
if not yaml_path.exists():
return [""]
try:
with open(yaml_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
return list(data.keys())
except Exception as e:
print(f"Error reading YAML {yaml_file}: {e}")
self._handle_corrupt_yaml(yaml_path)
return [""]
def _handle_corrupt_yaml(self, yaml_path: Path):
"""Handle corrupt YAML by renaming and creating new"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
bad_path = yaml_path.parent / f"bad_{timestamp}_{yaml_path.name}"
shutil.move(str(yaml_path), str(bad_path))
yaml_path.write_text("# Recovered from corrupt file\n")
def _start_watchdog(self):
"""Start file system observer"""
if self.observer is None:
self.observer = Observer()
self.observer.schedule(self.file_handler, str(self.yaml_dir), recursive=False)
self.observer.start()
def refresh_enums(self):
"""Refresh YAML and title enums, broadcast update"""
yaml_files = self._get_yaml_files()
# Build enum data for all YAML files
enum_data = {
"yaml_files": yaml_files,
"titles_by_yaml": {}
}
for yaml_file in yaml_files:
titles = self._get_titles_from_yaml(yaml_file)
enum_data["titles_by_yaml"][yaml_file] = titles
self._broadcast_enum(enum_data)
def _broadcast_enum(self, enum_data: Dict):
"""Broadcast enum update to frontend"""
if hasattr(self.server, 'send_sync'):
self.server.send_sync("ns_promptlist_enum", enum_data)
else:
# Alternative method for older ComfyUI versions
try:
from aiohttp import web
if hasattr(self.server, 'socketio'):
self.server.socketio.emit("ns_promptlist_enum", enum_data)
except:
pass
def _register_socket_handlers(self):
"""Register socket.io handlers"""
server = PromptServer.instance
@server.routes.post("/ns_promptlist/get_prompt")
async def get_prompt(request):
data = await request.json()
yaml_file = data.get("yaml", "")
title = data.get("title", "")
node_id = data.get("node_id", None)
response_data = self._get_prompt_data(yaml_file, title)
# Include node_id in response
response_data["node_id"] = node_id
# Send via websocket
if hasattr(server, 'send_sync'):
server.send_sync("ns_promptlist_set_widgets", response_data)
return {"success": True}
@server.routes.post("/ns_promptlist/reload_yamls")
async def reload_yamls(request):
"""Force reload YAML list"""
self.refresh_enums()
return {"success": True}
def _get_prompt_data(self, yaml_file: str, title: str) -> Dict[str, str]:
"""Get prompt data from YAML"""
yaml_path = self.yaml_dir / yaml_file
if not yaml_path.exists():
return {"title": title, "prompt": ""}
try:
with open(yaml_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
if title in data and isinstance(data[title], dict):
prompt = data[title].get("prompt", "")
else:
prompt = ""
return {"title": title, "prompt": prompt}
except Exception as e:
print(f"Error reading prompt: {e}")
return {"title": title, "prompt": ""}
def _save_yaml(self, yaml_file: str, data: Dict):
"""Atomic save YAML with lock"""
with self.write_lock:
yaml_path = self.yaml_dir / yaml_file
# Use temporary file for atomic write
with tempfile.NamedTemporaryFile(mode='w', dir=str(self.yaml_dir),
delete=False, encoding='utf-8') as tmp:
yaml.dump(data, tmp, default_flow_style=False, allow_unicode=True,
sort_keys=True)
tmp_path = tmp.name
# Atomic replace
os.replace(tmp_path, str(yaml_path))
def delete_title(self, yaml_file: str, title: str):
"""Delete a title from YAML"""
yaml_path = self.yaml_dir / yaml_file
if not yaml_path.exists():
return
try:
with open(yaml_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
if title in data:
del data[title]
# Run sync save
self._save_yaml(yaml_file, data)
self.refresh_enums()
except Exception as e:
print(f"Error deleting title: {e}")
def run(self, select_yaml: str, select: str, title: str, prompt: str,
unique_id: str) -> Tuple[str]:
"""Main execution function"""
# Check prompt length warning
if len(prompt) > 4096:
print(f"Warning: Prompt length ({len(prompt)} chars) exceeds recommended 4096 chars")
# Save current prompt to YAML
if title and prompt:
yaml_path = self.yaml_dir / select_yaml
try:
# Read existing data
if yaml_path.exists():
with open(yaml_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f) or {}
else:
data = {}
# Update or create entry
if title not in data:
data[title] = {}
data[title]["prompt"] = prompt
# Save
self._save_yaml(select_yaml, data)
except Exception as e:
print(f"Error saving prompt: {e}")
return (prompt,)
@classmethod
def IS_CHANGED(cls, **kwargs):
"""Force re-execution on each run"""
# This ensures the node re-validates inputs each time
return float("NaN")
@classmethod
def VALIDATE_INPUTS(cls, select_yaml, select, title, prompt, unique_id):
"""Validate inputs - always return True to avoid validation errors"""
# Since we're dynamically updating the select options,
# we need to bypass ComfyUI's static validation
return True
# Singleton instance
_instance = None
def get_instance():
global _instance
if _instance is None:
_instance = NS_PromptList()
return _instance
NODE_CLASS_MAPPINGS = {
"NS-PromptList": NS_PromptList
}
NODE_DISPLAY_NAME_MAPPINGS = {
"NS-PromptList": "NS Prompt List"
}
# Initialize instance on import
get_instance()
-3
View File
@@ -1,3 +0,0 @@
input as prompt name:
positive: input as positive prompt
negative: input as negative prompt
+5 -9
View File
@@ -1,17 +1,13 @@
[project]
name = "comfyui-promptlist"
description = "Custom node to manage prompts in YAML format."
<<<<<<< HEAD
version = "1.2.0"
=======
version = "1.1.2"
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
name = "comfyui-NS-promptlist"
description = "CustomNode to manage prompts in YAML format."
version = "2.0.0"
license = { file = "LICENSE.txt" }
[project.urls]
Repository = "https://github.com/NakamuraShippo/ComfyUI-PromptList"
Repository = "https://github.com/NakamuraShippo/ComfyUI-NS-PromptList"
[tool.comfy]
PublisherId = "nakamurashippo"
DisplayName = "ComfyUI-PromptList"
DisplayName = "ComfyUI-NS-PromptList"
Icon = ""
+3
View File
@@ -0,0 +1,3 @@
pyyaml>=6.0
watchdog>=3.0.0
filelock>=3.12.0
+315
View File
@@ -0,0 +1,315 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
import { api } from "../../../scripts/api.js";
// Store for YAML/title data
const promptListStore = {
yamlFiles: [],
titlesByYaml: {},
};
// Helper to find widget by name
function findWidget(node, name) {
return node.widgets?.find(w => w.name === name);
}
// Helper to refresh node display
function refreshNode(node) {
if (!node) return;
const previousSize = Array.isArray(node.size) ? [...node.size] : null;
if (ComfyWidgets?.refreshNode) {
ComfyWidgets.refreshNode(node);
} else {
const size = node.computeSize?.();
if (Array.isArray(size)) {
if (typeof node.setSize === "function") {
node.setSize(size);
} else {
node.size = [...size];
}
}
app.graph.setDirtyCanvas(true, true);
}
if (!previousSize) return;
const targetSize = [previousSize[0], previousSize[1]];
if (typeof node.setSize === "function") {
node.setSize(targetSize);
} else if (Array.isArray(node.size)) {
node.size[0] = targetSize[0];
node.size[1] = targetSize[1];
} else {
node.size = [...targetSize];
}
app.graph.setDirtyCanvas(true, true);
}
// Setup socket listeners
function setupSocketListeners() {
// Listen for enum updates
api.addEventListener("ns_promptlist_enum", (event) => {
const data = event.detail;
promptListStore.yamlFiles = data.yaml_files || [];
promptListStore.titlesByYaml = data.titles_by_yaml || {};
// Update all NS-PromptList nodes
app.graph._nodes.forEach(node => {
if (node.type === "NS-PromptList") {
updateNodeEnums(node);
}
});
});
// Listen for widget updates
api.addEventListener("ns_promptlist_set_widgets", (event) => {
const data = event.detail;
const targetNodeId = data.node_id;
// Only update the specific node if node_id is provided
if (targetNodeId) {
const targetNode = app.graph._nodes.find(n => n.id === targetNodeId);
if (targetNode && targetNode.type === "NS-PromptList") {
const titleWidget = findWidget(targetNode, "title");
const promptWidget = findWidget(targetNode, "prompt");
if (titleWidget) titleWidget.value = data.title || "";
if (promptWidget) promptWidget.value = data.prompt || "";
refreshNode(targetNode);
}
} else {
// Fallback: update active node only
const activeNode = app.canvas.node_over || app.canvas.selected_nodes?.[0];
if (activeNode && activeNode.type === "NS-PromptList") {
const titleWidget = findWidget(activeNode, "title");
const promptWidget = findWidget(activeNode, "prompt");
if (titleWidget) titleWidget.value = data.title || "";
if (promptWidget) promptWidget.value = data.prompt || "";
refreshNode(activeNode);
}
}
});
}
// Force reload YAML list
async function reloadYamlList() {
try {
// This will trigger the backend to refresh and broadcast new enum data
await api.fetchApi("/ns_promptlist/reload_yamls", {
method: "POST",
headers: { "Content-Type": "application/json" }
});
} catch (error) {
console.error("Error reloading YAML list:", error);
}
}
// Update node combo box options
function updateNodeEnums(node) {
const yamlWidget = findWidget(node, "select_yaml");
const selectWidget = findWidget(node, "select");
if (yamlWidget && promptListStore.yamlFiles.length > 0) {
// Update YAML options
const currentYaml = yamlWidget.value;
yamlWidget.options.values = promptListStore.yamlFiles;
// Keep current selection if it still exists
if (promptListStore.yamlFiles.includes(currentYaml)) {
yamlWidget.value = currentYaml;
} else {
yamlWidget.value = promptListStore.yamlFiles[0];
}
}
if (selectWidget && yamlWidget) {
// Update title options based on current YAML
const currentTitle = selectWidget.value;
const titles = promptListStore.titlesByYaml[yamlWidget.value] || [""];
selectWidget.options.values = titles;
// Keep current selection if it still exists
if (titles.includes(currentTitle)) {
selectWidget.value = currentTitle;
} else if (titles[0] && titles[0] !== "") {
selectWidget.value = titles[0];
// Fetch prompt for the new selection
requestPromptData(yamlWidget.value, titles[0], node.id);
} else {
selectWidget.value = "";
}
}
refreshNode(node);
}
// Hook select widget change handler
function hookSelectChange(node) {
if (node.type !== "NS-PromptList") return;
const yamlWidget = findWidget(node, "select_yaml");
const selectWidget = findWidget(node, "select");
// Helper to get original callback
function getOriginalCallback(widget) {
if (!widget || !widget.callback) return null;
// If it's our wrapper, return the original
if (widget.callback._originalCallback) {
return widget.callback._originalCallback;
}
return widget.callback;
}
// Store original callbacks (unwrapped)
const originalYamlCallback = getOriginalCallback(yamlWidget);
const originalSelectCallback = getOriginalCallback(selectWidget);
// Hook YAML selection change
if (yamlWidget) {
const newCallback = function (value) {
// Call original if exists
if (originalYamlCallback) {
originalYamlCallback.call(this, value);
}
// Update title options for this specific node
const titles = promptListStore.titlesByYaml[value] || [""];
if (selectWidget) {
selectWidget.options.values = titles;
// Auto-select first title if available
selectWidget.value = titles[0] || "";
// If we have a title, fetch its prompt
if (titles[0]) {
requestPromptData(value, titles[0], node.id);
}
}
refreshNode(node);
};
// Store original for future unwrapping
newCallback._originalCallback = originalYamlCallback;
yamlWidget.callback = newCallback;
}
// Hook title selection change
if (selectWidget) {
const newCallback = function (value) {
// Call original if exists
if (originalSelectCallback) {
originalSelectCallback.call(this, value);
}
// Fetch prompt data for this specific node
if (value) {
requestPromptData(yamlWidget?.value || "", value, node.id);
}
};
// Store original for future unwrapping
newCallback._originalCallback = originalSelectCallback;
selectWidget.callback = newCallback;
}
}
// Request prompt data from backend
async function requestPromptData(yamlFile, title, nodeId = null) {
if (!yamlFile || !title) return;
try {
await api.fetchApi("/ns_promptlist/get_prompt", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
yaml: yamlFile,
title: title,
node_id: nodeId
})
});
} catch (error) {
console.error("Error fetching prompt data:", error);
}
}
// Extension registration
app.registerExtension({
name: "NS.PromptList",
async setup() {
// Setup socket listeners
setupSocketListeners();
// Request initial YAML list on setup
setTimeout(async () => {
await reloadYamlList();
}, 500);
// Hook into node addition
const origNodeAdded = app.graph.onNodeAdded;
app.graph.onNodeAdded = function (node) {
if (origNodeAdded) {
origNodeAdded.call(this, node);
}
// Hook our node type
if (node.type === "NS-PromptList") {
setTimeout(() => {
hookSelectChange(node);
updateNodeEnums(node);
// Request fresh data when node is added
reloadYamlList();
}, 0);
}
};
},
async nodeCreated(node) {
if (node.type === "NS-PromptList") {
// Set minimum size for the node
node.size = [400, 300];
node.computeSize = function () {
const size = LGraphNode.prototype.computeSize.apply(this, arguments);
// Ensure minimum width and height
size[0] = Math.max(size[0], 400);
size[1] = Math.max(size[1], 300);
return size;
};
// Add delete button
const deleteButton = node.addWidget("button", "delete_title", "", () => {
const yamlWidget = findWidget(node, "select_yaml");
const titleWidget = findWidget(node, "title");
if (yamlWidget?.value && titleWidget?.value) {
if (confirm(`Delete title "${titleWidget.value}" from ${yamlWidget.value}?`)) {
// Call backend delete (would need to implement API endpoint)
console.log("Delete not implemented in this version");
}
}
});
// Initialize with first title after a short delay
setTimeout(() => {
const yamlWidget = findWidget(node, "select_yaml");
const selectWidget = findWidget(node, "select");
if (yamlWidget && selectWidget) {
const currentYaml = yamlWidget.value;
const titles = promptListStore.titlesByYaml[currentYaml];
if (titles && titles.length > 0 && titles[0] !== "") {
selectWidget.value = titles[0];
requestPromptData(currentYaml, titles[0], node.id);
}
}
}, 100);
}
}
});
+3
View File
@@ -0,0 +1,3 @@
pony_negative:
prompt: score_6up,score_5up,score_4up,score_3up,score_2up,score_1up,(worst quality,low
quality:1.5),disfigured, bad anatomy, draft, deformed hands, fused fingers,
+5
View File
@@ -0,0 +1,5 @@
pony_negative:
prompt: score_6up,score_5up,score_4up,score_3up,score_2up,score_1up,(worst quality,low
quality:1.5),disfigured, bad anatomy, draft, deformed hands, fused fingers,
pony_positive:
prompt: score_9, score_8_up, score_7_up, [anime style:source_anime:10],
+4
View File
@@ -0,0 +1,4 @@
sample1:
prompt: 'sample'
sample2:
prompt: 'sample2'
+31
View File
@@ -0,0 +1,31 @@
abstract_art:
prompt: 'abstract digital art, vibrant colors, geometric shapes,
fluid dynamics, modern composition, high contrast,
artistic expression, gallery quality'
anime_character:
prompt: 'anime style character, colorful hair, big expressive eyes,
detailed clothing, dynamic pose, studio lighting,
high quality illustration
'
cyberpunk_city:
prompt: 'futuristic cyberpunk cityscape, neon lights,
rain-slicked streets, towering skyscrapers,
flying vehicles, atmospheric fog, night scene
'
landscape_sunset:
prompt: 'golden hour landscape photography, dramatic clouds,
vibrant orange and purple sky, silhouetted trees,
wide angle lens, high dynamic range
'