File size: 6,159 Bytes
9c98083
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import os
import json
import folder_paths
import comfy.sd
import comfy.utils
from safetensors import safe_open
import hashlib
import urllib.request

CACHE_FILE = os.path.join(os.path.dirname(__file__), "dolphin_tags_cache.json")

def load_dolphin_cache():
    if os.path.exists(CACHE_FILE):
        try:
            with open(CACHE_FILE, "r", encoding="utf-8") as f:
                return json.load(f)
        except: return {}
    return {}

def save_dolphin_cache(cache_data):
    try:
        with open(CACHE_FILE, "w", encoding="utf-8") as f:
            json.dump(cache_data, f, indent=4)
    except: pass

class DolphinArchitectV15:
    @classmethod
    def INPUT_TYPES(s):
        loras = ["None"] + folder_paths.get_filename_list("loras")
        
        res = {
            "required": {
                "global_switch": ("BOOLEAN", {"default": True, "label_on": "GLOBAL ON", "label_off": "GLOBAL OFF (SKIP)"}),
                "model_base": ("MODEL",), "clip_base": ("CLIP",),
                "model_high": ("MODEL",), "clip_high": ("CLIP",),
                "model_low": ("MODEL",), "clip_low": ("CLIP",),
            },
            "optional": {}
        }

        # [๋‹ค์ด์–ดํŠธ ์™„๋ฃŒ] ์˜ค์ง ์ด๋ฆ„๊ณผ ์›จ์ดํŠธ๋งŒ ๋‚จ๊ฒจ์„œ ๊ฐ€์žฅ ์–‡์€ UI๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        res["optional"]["header_master"] = ("STRING", {"default": "โ”€โ”€โ”€ [ MASTER: B+H ] โ”€โ”€โ”€", "multiline": False})
        for i in range(1, 5): 
            res["optional"][f"master_l_{i}"] = (loras, {"default": "None"})
            res["optional"][f"master_b_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
            res["optional"][f"master_h_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})
            
        res["optional"]["header_low"] = ("STRING", {"default": "โ”€โ”€โ”€ [ LOW ONLY ] โ”€โ”€โ”€", "multiline": False})
        for i in range(1, 5): 
            res["optional"][f"low_l_{i}"] = (loras, {"default": "None"})
            res["optional"][f"low_w_{i}"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01})

        res["optional"]["manual_triggers"] = ("STRING", {"default": "", "multiline": True})
        return res

    RETURN_TYPES = ("MODEL", "CLIP", "MODEL", "CLIP", "MODEL", "CLIP", "STRING")
    RETURN_NAMES = ("M_BASE", "C_BASE", "M_HIGH", "C_HIGH", "M_LOW", "C_LOW", "triggers")
    FUNCTION = "exec"
    CATEGORY = "Dolphin"

    def get_trig(self, lora_name):
        if lora_name == "None": return []
        path = folder_paths.get_full_path("loras", lora_name)
        if not path or not os.path.exists(path): return []
        
        triggers = []
        try:
            with safe_open(path, framework="pt", device="cpu") as f:
                m = f.metadata()
                if m and "modelspec.trigger_words" in m:
                    tw = json.loads(m["modelspec.trigger_words"])
                    if isinstance(tw, list): triggers.extend(tw)
                    elif isinstance(tw, str): triggers.append(tw)
        except: pass
        if triggers: return triggers 

        cache = load_dolphin_cache()
        if lora_name in cache and cache[lora_name]: return cache[lora_name]

        try:
            sha256_hash = hashlib.sha256()
            with open(path, "rb") as f:
                for byte_block in iter(lambda: f.read(4096 * 1024), b""):
                    sha256_hash.update(byte_block)
            
            url = f"https://civitai.com/api/v1/model-versions/by-hash/{sha256_hash.hexdigest()}"
            req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
            with urllib.request.urlopen(req, timeout=5) as response:
                data = json.loads(response.read().decode('utf-8'))
                api_triggers = data.get("trainedWords", [])
                if api_triggers:
                    cache[lora_name] = api_triggers
                    save_dolphin_cache(cache)
                    return api_triggers
        except: pass
        return triggers

    def exec(self, global_switch, model_base, clip_base, model_high, clip_high, model_low, clip_low, manual_triggers="", **kwargs):
        mb, cb = model_base, clip_base
        mh, ch = model_high, clip_high
        ml, cl = model_low, clip_low
        
        if not global_switch:
            trigs = [t.strip() for t in manual_triggers.split(',') if t.strip()]
            ut = ", ".join(list(dict.fromkeys(trigs)))
            return (mb, cb, mh, ch, ml, cl, ut)

        applied_names = []

        for i in range(1, 5): 
            n = kwargs.get(f"master_l_{i}", "None")
            w_b = kwargs.get(f"master_b_w_{i}", 1.0)
            w_h = kwargs.get(f"master_h_w_{i}", 1.0)
            
            # [์ตœ์ ํ™”] None์ด ์•„๋‹ˆ๋ฉด์„œ, ์›จ์ดํŠธ๊ฐ€ 0.0์ด ์•„๋‹ ๋•Œ๋งŒ ์ ์šฉ (0.0์ด๋ฉด ์Šค์œ„์น˜ OFF ํšจ๊ณผ)
            if n != "None" and (w_b != 0.0 or w_h != 0.0):
                applied_names.append(n)
                lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
                mb, cb = comfy.sd.load_lora_for_models(mb, cb, lora_data, w_b, w_b)
                mh, ch = comfy.sd.load_lora_for_models(mh, ch, lora_data, w_h, w_h)

        for i in range(1, 5): 
            n = kwargs.get(f"low_l_{i}", "None")
            w = kwargs.get(f"low_w_{i}", 1.0)
            
            if n != "None" and w != 0.0:
                applied_names.append(n)
                lora_data = comfy.utils.load_torch_file(folder_paths.get_full_path("loras", n))
                ml, cl = comfy.sd.load_lora_for_models(ml, cl, lora_data, w, w)

        trigs = []
        for n in set(applied_names):
            trigs.extend(self.get_trig(n))
            
        if manual_triggers.strip():
            manual_list = [t.strip() for t in manual_triggers.split(',') if t.strip()]
            trigs.extend(manual_list)
        
        unique_trigs = ", ".join(list(dict.fromkeys(trigs)))
        
        return (mb, cb, mh, ch, ml, cl, unique_trigs)