Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc3baafa9c | ||
|
|
f69497e46b | ||
|
|
49b75c9bbb | ||
|
|
b81f1b6459 | ||
|
|
670b33ae2f | ||
|
|
b00debed9e | ||
|
|
79d7de96f4 | ||
|
|
6262b95cfb | ||
|
|
67c72d6797 | ||
|
|
9dbd32d5b6 | ||
|
|
f3836a2cd5 | ||
|
|
d6258d9a1f | ||
|
|
0c40477c3d | ||
|
|
cbe8d620e7 | ||
|
|
4d23e0d2ed | ||
|
|
32dd1e3852 | ||
|
|
f8ab9697cd | ||
|
|
580d24eaa3 | ||
|
|
12ddfcb2da | ||
|
|
d9d9027680 | ||
|
|
32d8e84995 | ||
|
|
a3a49d3699 | ||
|
|
5413f69be1 | ||
|
|
322dc6abf1 | ||
|
|
be63c40b5e | ||
|
|
b58a96009d | ||
|
|
714bdd2c5c | ||
|
|
4b4143a334 | ||
|
|
2303daa3f4 | ||
|
|
72ddf3dcff | ||
|
|
c12fdc9834 | ||
|
|
aeeb1926f6 | ||
|
|
0e2d7dd1ff | ||
|
|
48ae8b5aaf | ||
|
|
936d131b49 | ||
|
|
fd0ab4c64d | ||
|
|
4832ea3f89 | ||
|
|
d46577aef9 | ||
|
|
e5a2e08e35 | ||
|
|
eb3ec610e9 | ||
|
|
ed33237a43 | ||
|
|
c6bf627620 | ||
|
|
c0cf34d8c3 | ||
|
|
babe3b7894 | ||
|
|
7d9de1460c | ||
|
|
d734ae42fc | ||
|
|
4df8bd6ff5 | ||
|
|
3c42f923e4 |
@@ -4,21 +4,16 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- master
|
|
||||||
paths:
|
paths:
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-node:
|
publish-node:
|
||||||
name: Publish Custom Node to registry
|
name: Publish Custom Node to registry
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# if this is a forked repository. Skipping the workflow.
|
|
||||||
if: github.event.repository.fork == false
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Publish Custom Node
|
- name: Publish Custom Node
|
||||||
uses: Comfy-Org/publish-node-action@main
|
uses: Comfy-Org/publish-node-action@main
|
||||||
with:
|
with:
|
||||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
|
||||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||||
@@ -160,3 +160,9 @@ cython_debug/
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
#PromptList
|
||||||
|
yaml/
|
||||||
|
|
||||||
|
#CODEX
|
||||||
|
AGENTS.md
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
|
|
||||||

|
|
||||||
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
|
|
||||||

|
|
||||||
"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.
|
|
||||||

|
|
||||||
|
|
||||||
*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
@@ -1,5 +1,13 @@
|
|||||||
# ComfyUI-PromptList
|
# ComfyUI-NS-PromptList
|
||||||
ComfyUI-PromptListは、[ComfyUI](https://github.com/comfyanonymous/ComfyUI)用のprompts.yamlに記録したプロンプトを出力するシンプルなプロンプト管理ノードです。
|
このリポジトリは更新終了し、[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)をインストールしている場合
|
[ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager)をインストールしている場合
|
||||||
1. メインメニューのManager -> Install via Git URLの順にクリックする
|
1. メインメニューのManager -> Install via Git URLの順にクリックする
|
||||||
2. ウインドウ上部に出てくるテキストボックスにURLを貼り付けてOKを押す
|
2. ウインドウ上部に出てくるテキストボックスにURLを貼り付けてOKを押す
|
||||||
https://github.com/NakamuraShippo/ComfyUI-PromptList
|
https://github.com/NakamuraShippo/ComfyUI-NS-PromptList
|
||||||
3. インストールが完了したら、ComfyUIを再起動
|
3. インストールが完了したら、ComfyUIを再起動
|
||||||
|
|
||||||
[ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager)をインストールしていない場合
|
[ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager)をインストールしていない場合
|
||||||
1. ComfyUIのカスタムノードディレクトリに移動します。(通常は ComfyUI/custom_nodes/)
|
1. ComfyUIのカスタムノードディレクトリに移動します。(通常は ComfyUI/custom_nodes/)
|
||||||
2. このリポジトリをクローンします。
|
2. このリポジトリをクローンします。
|
||||||
`git clone https://github.com/NakamuraShippo/ComfyUI-PromptList`
|
`git clone https://github.com/NakamuraShippo/ComfyUI-NS-PromptList`
|
||||||
3. ComfyUIを再起動します。
|
3. ComfyUIを再起動します。
|
||||||
|
4. ComfyUI\venv\ScriptsでShift+右クリック→ターミナルで開く -> activateと入力
|
||||||
|
~~~
|
||||||
|
pip install pyyaml watchdog filelock
|
||||||
|
~~~
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||

|
ノードの場所
|
||||||
ワークスペース上で右クリック -> Add node-> prompt -> Prompt Listでノードを追加します。
|
Add Node -> NS -> NS 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)を用意しました。
|
|
||||||
|
|
||||||
#### スプレッドシートを利用する前準備
|
ノードのウィジェット
|
||||||
1. ファイル -> コピー作成 -> 好きなファイル名に編集し「コピーを作成」
|
- select_yaml : ComfyUI-Create\custom_nodes\ComfyUI-NS-PromptList\yaml にあるyamlのリスト、選んだyamlをロードします
|
||||||
2. しばらく待つとヘルプの右側に「エクスポート」が現れるので、エクスポート -> YAMLとしてエクスポート
|
- select : select_yamlで選択したyaml内のtitleキーの一覧を選択すると、titleと文字列を読み込みます
|
||||||
3. 認証が必要ですウインドウ -> OK
|
- title and text area : ロードされたtitleと文字列情報の内容です
|
||||||
4. googleアカウントを選択してログイン(警告が出た場合:詳細を表示 -> 安全ではないページに移動 -> 許可)
|
|
||||||
|
|
||||||
#### スプレッドシートの記述方法
|
プロンプトの登録方法
|
||||||

|
1. titleに識別用の見出しを入力します
|
||||||
「prompt name」のセル = prompt name
|
2. テキストエリアに任意の文字列を入力します
|
||||||
「positive」の右隣のセル = positive prompt
|
3. 生成するとyamlへ書き込まれます
|
||||||
「negative」の右隣のセル = negative prompt
|
|
||||||
をそれぞれ入力します。(A列とC列の白色セルが入力可能なセルとなっています)
|
|
||||||
|
|
||||||
登録する項目を増やしたい時はオートフィルをご利用ください。
|
|
||||||

|
|
||||||
|
|
||||||
*注意*
|
プロンプトの編集方法
|
||||||
グレーで塗りつぶされたセルは編集しないでください。
|
1. titleに編集したいプロンプトの名前を入力
|
||||||
|
2. テキストエリアに任意の文字列を入力
|
||||||
|
3. 生成
|
||||||
|
4. 指定したyaml内に同じtitle名があれば、文字列を上書きします
|
||||||
|
|
||||||
#### YAMLのエクスポート
|
誤って編集した場合
|
||||||
<<<<<<< HEAD
|
CTRL + Z でredoすると文字列が復元されるので、再度生成して上書きしてください
|
||||||
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を押してください。
|
|
||||||
|
|
||||||
## アップデート履歴
|
## アップデート履歴
|
||||||
<<<<<<< HEAD
|
2025/05/31 2.0.0 根本的に作り直しました。ComfyUI上で完結するようにしました。
|
||||||
2024/09/06 1.2.0 ノード入力欄をmultilineに修正。編集用スプレッドシートを公開。
|
2024/09/06 1.2.0 ノード入力欄をmultilineに修正。編集用スプレッドシートを公開。
|
||||||
2024/08/24 1.0.0 とりあえず動いてるので公開
|
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
@@ -1,21 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
@author: NakamuraShippo
|
ComfyUI-NS-PromptList
|
||||||
@title: ComfyUI-PromptList
|
A ComfyUI custom node for managing prompts with YAML files
|
||||||
@description: Custom node to manage prompts in YAML format.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .ComfyUI_PromptList import ComfyUI_PromptList
|
from .prompt_list_node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||||
"ComfyUI-PromptList": ComfyUI_PromptList
|
|
||||||
}
|
|
||||||
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
WEB_DIRECTORY = "./web"
|
||||||
"ComfyUI-PromptList": "Prompt List"
|
|
||||||
}
|
|
||||||
|
|
||||||
WEB_DIRECTORY = "./web"
|
|
||||||
|
|
||||||
print("ComfyUI-PromptList initialized")
|
|
||||||
|
|
||||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -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()
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
input as prompt name:
|
|
||||||
positive: input as positive prompt
|
|
||||||
negative: input as negative prompt
|
|
||||||
+5
-9
@@ -1,17 +1,13 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-promptlist"
|
name = "comfyui-NS-promptlist"
|
||||||
description = "Custom node to manage prompts in YAML format."
|
description = "CustomNode to manage prompts in YAML format."
|
||||||
<<<<<<< HEAD
|
version = "2.0.0"
|
||||||
version = "1.2.0"
|
|
||||||
=======
|
|
||||||
version = "1.1.2"
|
|
||||||
>>>>>>> aff35ca03de77cf5e7f6882b9993bd4c54ff2734
|
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Repository = "https://github.com/NakamuraShippo/ComfyUI-PromptList"
|
Repository = "https://github.com/NakamuraShippo/ComfyUI-NS-PromptList"
|
||||||
|
|
||||||
[tool.comfy]
|
[tool.comfy]
|
||||||
PublisherId = "nakamurashippo"
|
PublisherId = "nakamurashippo"
|
||||||
DisplayName = "ComfyUI-PromptList"
|
DisplayName = "ComfyUI-NS-PromptList"
|
||||||
Icon = ""
|
Icon = ""
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pyyaml>=6.0
|
||||||
|
watchdog>=3.0.0
|
||||||
|
filelock>=3.12.0
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
@@ -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],
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
sample1:
|
||||||
|
prompt: 'sample'
|
||||||
|
sample2:
|
||||||
|
prompt: 'sample2'
|
||||||
@@ -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
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
Reference in New Issue
Block a user