Initial public release prep
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
data/libraries/
|
||||
docs/dev/
|
||||
*.psd
|
||||
*.clip
|
||||
*.kra
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
data/libraries/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 KAZUYA
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# ComfyUI Visual Prompt Library
|
||||
|
||||
Thumbnail-based visual prompt part picker and library manager for ComfyUI.
|
||||
|
||||
This custom node is meant for building a personal visual prompt dictionary: expressions, camera angles, lighting, backgrounds, poses, outfits, accessories, and other prompt fragments can be stored with thumbnails and selected visually inside a workflow.
|
||||
|
||||
## Features
|
||||
|
||||
- `Visual Prompt Picker` node outputs selected prompt fragments as one `STRING`.
|
||||
- Sidebar library manager for editing shared JSON databases.
|
||||
- Category tags and prompt tags.
|
||||
- Thumbnail support with file upload, data URL, and recent ComfyUI output selection.
|
||||
- Import and export JSON libraries.
|
||||
- Search within the selected category.
|
||||
- Remembers the last selected DB, category, and view mode in the browser.
|
||||
- Three view modes: large thumbnail, small thumbnail, and text.
|
||||
|
||||
## Why This Exists
|
||||
|
||||
Many prompt selector tools focus on text tags, styles, wildcards, or preset buttons. This project focuses on a visual prompt dictionary workflow:
|
||||
|
||||
- See a thumbnail.
|
||||
- Pick the prompt part.
|
||||
- Send the selected fragments into the ComfyUI graph as a `STRING`.
|
||||
|
||||
It is especially useful for prompt parts that are hard to remember from text alone, such as facial expressions, poses, camera composition, and background types.
|
||||
|
||||
## Installation
|
||||
|
||||
Clone this repository into `ComfyUI/custom_nodes`.
|
||||
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/kazuya-bros/ComfyUI-VisualPromptLibrary.git
|
||||
```
|
||||
|
||||
Restart ComfyUI after installing or updating Python routes.
|
||||
|
||||
For JavaScript-only changes, a browser hard reload is usually enough.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Add `Visual Prompt Picker` to your workflow.
|
||||
2. Open the `Visual Prompt Library` sidebar tab.
|
||||
3. Create or select a DB.
|
||||
4. Add category tags, such as `表情`, `画角`, or `背景`.
|
||||
5. Add prompt tags with labels, prompt text, and optional thumbnails.
|
||||
6. In the node UI, load the DB and click prompt cards.
|
||||
7. Connect the `prompt` output to a text prompt node.
|
||||
|
||||
Selected fragments are joined by the node's `delimiter` setting.
|
||||
|
||||
## Library JSON
|
||||
|
||||
Shared databases are stored locally under:
|
||||
|
||||
```text
|
||||
data/libraries/*.json
|
||||
```
|
||||
|
||||
This folder is ignored by git so private prompt libraries are not published accidentally.
|
||||
|
||||
A small starter sample is included:
|
||||
|
||||
```text
|
||||
data/sample_visual_prompt_library.json
|
||||
```
|
||||
|
||||
Each category contains prompt parts:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "gentle_smile",
|
||||
"label": "微笑み",
|
||||
"prompt": "gentle smile, relaxed eyes, closed mouth",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
}
|
||||
```
|
||||
|
||||
`thumbnail` can be empty, a file path, a ComfyUI `/view?...` URL, or a data URL. The manager UI can also resize a selected image into a compact data URL.
|
||||
|
||||
## Sample Data Policy
|
||||
|
||||
The included sample prompts are intentionally small and original. Do not copy paid prompt dictionaries, books, or third-party datasets verbatim into a public repository unless you have permission.
|
||||
|
||||
For personal use, imported JSON libraries stay in `data/libraries/`.
|
||||
|
||||
## Publishing Status
|
||||
|
||||
This repository is prepared for personal GitHub publishing.
|
||||
|
||||
Before publishing to Comfy Registry:
|
||||
|
||||
- Confirm that `PublisherId = "kazuya-bros"` matches your Comfy Registry publisher ID.
|
||||
- Add screenshots.
|
||||
- Confirm the package name and version.
|
||||
|
||||
See:
|
||||
|
||||
```text
|
||||
docs/publishing-checklist.md
|
||||
```
|
||||
|
||||
## Similar Tools
|
||||
|
||||
This project overlaps with prompt selector and prompt management tools, but the intended focus is different:
|
||||
|
||||
- Text prompt selectors and style selectors are good for preset text workflows.
|
||||
- Wildcard tools are good for random or combinatorial prompt generation.
|
||||
- Visual Prompt Library is for manually choosing prompt fragments from thumbnails and returning them as graph output.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,5 @@
|
||||
from .visual_prompt_picker import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"version": 1,
|
||||
"categories": [
|
||||
{
|
||||
"id": "expression",
|
||||
"label": "Expression",
|
||||
"parts": [
|
||||
{
|
||||
"id": "gentle_smile",
|
||||
"label": "微笑み",
|
||||
"prompt": "gentle smile, relaxed eyes, closed mouth",
|
||||
"negative": "angry expression, crying, open mouth",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "angry",
|
||||
"label": "怒り",
|
||||
"prompt": "angry expression, narrowed eyes, tense mouth",
|
||||
"negative": "gentle smile, relaxed eyes",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "surprised",
|
||||
"label": "驚き",
|
||||
"prompt": "surprised expression, wide eyes, small open mouth",
|
||||
"negative": "sleepy expression, closed eyes",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "crying",
|
||||
"label": "泣き",
|
||||
"prompt": "crying, tearful eyes, trembling lips",
|
||||
"negative": "smile, happy expression",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "sleepy",
|
||||
"label": "眠そう",
|
||||
"prompt": "sleepy expression, half-closed eyes",
|
||||
"negative": "wide eyes, surprised expression",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "embarrassed",
|
||||
"label": "照れ",
|
||||
"prompt": "embarrassed blush, looking away, small smile",
|
||||
"negative": "angry expression, crying",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"version": 1,
|
||||
"meta": {
|
||||
"title": "Sample Visual Prompt Library",
|
||||
"description": "Small starter library for testing Visual Prompt Library. Prompts are original short examples."
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": "expression",
|
||||
"label": "表情",
|
||||
"parts": [
|
||||
{
|
||||
"id": "expressionless",
|
||||
"label": "無表情",
|
||||
"prompt": "expressionless, neutral face, relaxed mouth",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "gentle_smile",
|
||||
"label": "微笑み",
|
||||
"prompt": "gentle smile, relaxed eyes, closed mouth",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "angry",
|
||||
"label": "怒り",
|
||||
"prompt": "angry expression, narrowed eyes, tense mouth",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "embarrassed",
|
||||
"label": "照れ",
|
||||
"prompt": "embarrassed blush, looking away, small smile",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "camera_composition",
|
||||
"label": "画角 / 構図",
|
||||
"parts": [
|
||||
{
|
||||
"id": "portrait_close_up",
|
||||
"label": "顔アップ",
|
||||
"prompt": "close-up portrait, face focus",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "upper_body",
|
||||
"label": "上半身",
|
||||
"prompt": "upper body shot, waist up",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "full_body",
|
||||
"label": "全身",
|
||||
"prompt": "full body, standing pose, entire character visible",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "low_angle",
|
||||
"label": "ローアングル",
|
||||
"prompt": "low angle view, dynamic perspective",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lighting",
|
||||
"label": "光",
|
||||
"parts": [
|
||||
{
|
||||
"id": "soft_light",
|
||||
"label": "柔らかい光",
|
||||
"prompt": "soft lighting, gentle shadows",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "rim_light",
|
||||
"label": "リムライト",
|
||||
"prompt": "rim lighting, highlighted silhouette",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "backlight",
|
||||
"label": "逆光",
|
||||
"prompt": "backlighting, glowing edge light",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "background",
|
||||
"label": "背景",
|
||||
"parts": [
|
||||
{
|
||||
"id": "simple_room",
|
||||
"label": "室内",
|
||||
"prompt": "simple room interior, clean background",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "city_street",
|
||||
"label": "街",
|
||||
"prompt": "city street background, urban scenery",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
},
|
||||
{
|
||||
"id": "forest",
|
||||
"label": "森",
|
||||
"prompt": "forest background, natural scenery",
|
||||
"negative": "",
|
||||
"thumbnail": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Publishing Checklist
|
||||
|
||||
This project is currently prepared for personal GitHub publishing first.
|
||||
|
||||
## Before Pushing
|
||||
|
||||
- Confirm that `PublisherId = "kazuya-bros"` matches your Comfy Registry publisher ID before Registry publishing.
|
||||
- Add screenshots to the README if available.
|
||||
- Keep private libraries out of git. `data/libraries/` is ignored.
|
||||
- Do not copy third-party prompt dictionaries verbatim into sample data.
|
||||
|
||||
## Registry Later
|
||||
|
||||
- Create a publisher at Comfy Registry.
|
||||
- Create a Registry API key.
|
||||
- Run `comfy node publish` from this directory, or set up GitHub Actions.
|
||||
- Bump `version` in `pyproject.toml` for each release.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
[project]
|
||||
name = "visual-prompt-library"
|
||||
description = "Thumbnail-based visual prompt part picker and library manager for ComfyUI."
|
||||
version = "0.1.0"
|
||||
license = { file = "LICENSE" }
|
||||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
classifiers = [
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/kazuya-bros/ComfyUI-VisualPromptLibrary"
|
||||
"Bug Tracker" = "https://github.com/kazuya-bros/ComfyUI-VisualPromptLibrary/issues"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "kazuya-bros"
|
||||
DisplayName = "Visual Prompt Library"
|
||||
@@ -0,0 +1,273 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from aiohttp import web
|
||||
from server import PromptServer
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
except Exception:
|
||||
folder_paths = None
|
||||
|
||||
|
||||
NODE_DIR = Path(__file__).resolve().parent
|
||||
DEFAULT_LIBRARY_PATH = NODE_DIR / "data" / "expression_parts.json"
|
||||
LIBRARIES_DIR = NODE_DIR / "data" / "libraries"
|
||||
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
|
||||
|
||||
|
||||
def load_default_library():
|
||||
try:
|
||||
return json.loads(DEFAULT_LIBRARY_PATH.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {"categories": []}
|
||||
|
||||
|
||||
DEFAULT_LIBRARY = load_default_library()
|
||||
|
||||
|
||||
def safe_library_name(name):
|
||||
cleaned = "".join(c for c in str(name or "default") if c.isalnum() or c in ("-", "_")).strip()
|
||||
return cleaned or "default"
|
||||
|
||||
|
||||
def library_path(name):
|
||||
LIBRARIES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
return LIBRARIES_DIR / f"{safe_library_name(name)}.json"
|
||||
|
||||
|
||||
def get_output_directory():
|
||||
if folder_paths is not None:
|
||||
try:
|
||||
return Path(folder_paths.get_output_directory()).resolve()
|
||||
except Exception:
|
||||
pass
|
||||
return (NODE_DIR.parent.parent / "output").resolve()
|
||||
|
||||
|
||||
def ensure_default_library_file():
|
||||
path = library_path("default")
|
||||
if not path.exists():
|
||||
path.write_text(json.dumps(DEFAULT_LIBRARY, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def read_library_file(name):
|
||||
path = library_path(name)
|
||||
if not path.exists():
|
||||
return None
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def write_library_file(name, payload):
|
||||
categories = payload.get("categories") if isinstance(payload, dict) else None
|
||||
if not isinstance(categories, list):
|
||||
raise ValueError("library JSON must contain a categories array")
|
||||
|
||||
normalized = {
|
||||
"version": payload.get("version", 1),
|
||||
"meta": payload.get("meta", {}),
|
||||
"categories": categories,
|
||||
}
|
||||
path = library_path(name)
|
||||
path.write_text(json.dumps(normalized, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return normalized
|
||||
|
||||
|
||||
def list_library_files():
|
||||
ensure_default_library_file()
|
||||
items = []
|
||||
for path in sorted(LIBRARIES_DIR.glob("*.json")):
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
data = {}
|
||||
categories = data.get("categories") if isinstance(data, dict) else []
|
||||
parts_count = 0
|
||||
if isinstance(categories, list):
|
||||
for category in categories:
|
||||
if isinstance(category, dict) and isinstance(category.get("parts"), list):
|
||||
parts_count += len(category["parts"])
|
||||
items.append(
|
||||
{
|
||||
"name": path.stem,
|
||||
"label": data.get("meta", {}).get("title") or path.stem,
|
||||
"categories": len(categories) if isinstance(categories, list) else 0,
|
||||
"parts": parts_count,
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def list_recent_output_images(limit=80):
|
||||
output_dir = get_output_directory()
|
||||
if not output_dir.exists():
|
||||
return []
|
||||
|
||||
files = []
|
||||
for path in output_dir.rglob("*"):
|
||||
if not path.is_file() or path.suffix.lower() not in IMAGE_EXTENSIONS:
|
||||
continue
|
||||
try:
|
||||
stat = path.stat()
|
||||
rel = path.relative_to(output_dir)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
subfolder = "" if rel.parent == Path(".") else rel.parent.as_posix()
|
||||
query = {
|
||||
"filename": rel.name,
|
||||
"type": "output",
|
||||
}
|
||||
if subfolder:
|
||||
query["subfolder"] = subfolder
|
||||
|
||||
files.append(
|
||||
{
|
||||
"filename": rel.name,
|
||||
"subfolder": subfolder,
|
||||
"mtime": stat.st_mtime,
|
||||
"size": stat.st_size,
|
||||
"url": f"/view?{urlencode(query)}",
|
||||
}
|
||||
)
|
||||
|
||||
files.sort(key=lambda item: item["mtime"], reverse=True)
|
||||
return files[: max(1, min(int(limit or 80), 200))]
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/visual_prompt_library/libraries")
|
||||
async def vpl_list_libraries(_request):
|
||||
return web.json_response({"libraries": list_library_files()})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/visual_prompt_library/recent_outputs")
|
||||
async def vpl_recent_outputs(request):
|
||||
limit = request.query.get("limit", "80")
|
||||
try:
|
||||
images = list_recent_output_images(limit)
|
||||
except Exception as error:
|
||||
return web.json_response({"error": str(error)}, status=400)
|
||||
return web.json_response({"images": images, "output_dir": str(get_output_directory())})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/visual_prompt_library/open_output_folder")
|
||||
async def vpl_open_output_folder(_request):
|
||||
output_dir = get_output_directory()
|
||||
try:
|
||||
if os.name == "nt":
|
||||
os.startfile(str(output_dir))
|
||||
else:
|
||||
import subprocess
|
||||
|
||||
subprocess.Popen(["xdg-open", str(output_dir)])
|
||||
except Exception as error:
|
||||
return web.json_response({"error": str(error)}, status=400)
|
||||
return web.json_response({"opened": True, "output_dir": str(output_dir)})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/visual_prompt_library/libraries/{name}")
|
||||
async def vpl_get_library(request):
|
||||
name = safe_library_name(request.match_info.get("name"))
|
||||
data = read_library_file(name)
|
||||
if data is None:
|
||||
return web.json_response({"error": "library not found"}, status=404)
|
||||
return web.json_response({"name": name, "library": data})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/visual_prompt_library/libraries/{name}")
|
||||
async def vpl_save_library(request):
|
||||
name = safe_library_name(request.match_info.get("name"))
|
||||
try:
|
||||
payload = await request.json()
|
||||
library = payload.get("library", payload) if isinstance(payload, dict) else payload
|
||||
saved = write_library_file(name, library)
|
||||
except Exception as error:
|
||||
return web.json_response({"error": str(error)}, status=400)
|
||||
return web.json_response({"name": name, "library": saved})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.delete("/visual_prompt_library/libraries/{name}")
|
||||
async def vpl_delete_library(request):
|
||||
name = safe_library_name(request.match_info.get("name"))
|
||||
if name == "default":
|
||||
return web.json_response({"error": "default library cannot be deleted"}, status=400)
|
||||
path = library_path(name)
|
||||
if not path.exists():
|
||||
return web.json_response({"error": "library not found"}, status=404)
|
||||
try:
|
||||
path.unlink()
|
||||
except Exception as error:
|
||||
return web.json_response({"error": str(error)}, status=400)
|
||||
return web.json_response({"name": name, "deleted": True})
|
||||
|
||||
|
||||
class VisualPromptPicker:
|
||||
DESCRIPTION = "Thumbnail-driven prompt part picker. Click visual cards to emit selected prompt fragments."
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"delimiter": ("STRING", {"default": ", ", "multiline": False}),
|
||||
"preview_prompt": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
"hidden": {
|
||||
"selected_parts": ("STRING", {"default": ""}),
|
||||
"library_json": ("STRING", {"default": json.dumps(DEFAULT_LIBRARY, ensure_ascii=False)}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
FUNCTION = "build_prompt"
|
||||
CATEGORY = "prompt/visual"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, delimiter, preview_prompt="", **kwargs):
|
||||
payload = json.dumps(
|
||||
{
|
||||
"delimiter": delimiter,
|
||||
"selected_parts": kwargs.get("selected_parts", ""),
|
||||
"library_json": kwargs.get("library_json", ""),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
)
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
def build_prompt(self, delimiter, preview_prompt="", **kwargs):
|
||||
selected_raw = kwargs.get("selected_parts") or ""
|
||||
|
||||
try:
|
||||
state = json.loads(selected_raw) if selected_raw else {}
|
||||
except json.JSONDecodeError:
|
||||
state = {}
|
||||
|
||||
selected = state.get("selected", [])
|
||||
if not isinstance(selected, list):
|
||||
selected = []
|
||||
|
||||
fragments = []
|
||||
|
||||
for item in selected:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
|
||||
prompt = str(item.get("prompt", "")).strip()
|
||||
if prompt:
|
||||
fragments.append(prompt)
|
||||
|
||||
prompt = delimiter.join([p for p in fragments if p])
|
||||
return (prompt,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"VisualPromptPicker": VisualPromptPicker,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VisualPromptPicker": "Visual Prompt Picker",
|
||||
}
|
||||
Reference in New Issue
Block a user