File size: 3,158 Bytes
38fe5dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""JoyEcho Script Picker — a dropdown node for selecting a saved prompt JSON.

The stock JoyEcho_TextEncode 'prompts' field only accepts pasted JSON / a typed
path, which is painful in the canvas. This node lists every *.json in
  <ComfyUI>/input/joyecho_prompts/
as a COMBO dropdown and outputs the file's contents, wired straight into
JoyEcho_TextEncode's 'prompts' input (which accepts inline {"prompts":[...]} JSON).

Add a .json to that folder, hit the ComfyUI refresh button (or press R) to
repopulate the dropdown, pick it, run. Editing the file re-triggers execution
automatically (IS_CHANGED tracks mtime) — no need to reselect.
"""

import json
from pathlib import Path

import folder_paths

_PROMPTS_SUBDIR = "joyecho_prompts"
_EMPTY = "(no .json in input/joyecho_prompts)"


def _scripts_dir() -> Path:
    d = Path(folder_paths.get_input_directory()) / _PROMPTS_SUBDIR
    try:
        d.mkdir(parents=True, exist_ok=True)
    except OSError:
        pass
    return d


def _list_scripts() -> list[str]:
    d = _scripts_dir()
    try:
        files = sorted(p.name for p in d.glob("*.json"))
    except OSError:
        files = []
    return files if files else [_EMPTY]


class JoyEcho_ScriptPicker:
    """Pick a prompt-script .json from input/joyecho_prompts via a dropdown."""

    @classmethod
    def INPUT_TYPES(cls):
        return {"required": {"script": (_list_scripts(),)}}

    RETURN_TYPES = ("STRING", "STRING",)
    RETURN_NAMES = ("prompts_json", "path",)
    FUNCTION = "load"
    CATEGORY = "JoyAI-Echo"

    @classmethod
    def IS_CHANGED(cls, script):
        # Re-run when the selected file changes on disk, so edits are picked up.
        p = _scripts_dir() / script
        try:
            return f"{script}:{p.stat().st_mtime}"
        except OSError:
            return script

    def load(self, script):
        if script == _EMPTY:
            raise ValueError(
                f"No .json scripts found. Put your prompt JSON in {_scripts_dir()} "
                "and press the refresh button (or R) to repopulate the dropdown."
            )
        p = _scripts_dir() / script
        if not p.exists():
            raise FileNotFoundError(
                f"Script not found: {p}. Refresh the node list (R) after adding files."
            )
        text = p.read_text(encoding="utf-8")
        # Fail early with a clear message rather than deep in the text encoder.
        try:
            data = json.loads(text)
        except json.JSONDecodeError as e:
            raise ValueError(f"{script} is not valid JSON: {e}")
        arr = data.get("prompts") if isinstance(data, dict) else None
        if arr is None and isinstance(data, dict):
            arr = data.get("shots")
        if not isinstance(arr, list) or not arr:
            raise ValueError(f"{script} must contain a non-empty 'prompts' (or 'shots') array.")
        print(f"[JoyEcho] ScriptPicker: {script} ({len(arr)} shots).", flush=True)
        return (text, str(p),)


NODE_CLASS_MAPPINGS = {"JoyEcho_ScriptPicker": JoyEcho_ScriptPicker}
NODE_DISPLAY_NAME_MAPPINGS = {"JoyEcho_ScriptPicker": "JoyEcho Script Picker (JSON dropdown)"}