CoolFace
Datasetpublic

Mightys/Notebook_Scripts

sourceHugging Faceupdated 12d agoView on Hugging Face
0likes2.7kdownloads
gestor_swarm.py550 linesDownload Raw Back to Swarmui_Lightning
1import ipywidgets as widgets2from IPython.display import display, clear_output, HTML3import os4import subprocess5import requests6import re7import pickle8from pathlib import Path9 10# --- Diccionarios de Modelos Predefinidos ---11PRESET_MODELS = {12    "WaiNSFW V16": "https://huggingface.co/Quiho/best-from-civitai/resolve/main/waiIllustriousSDXL_v160.safetensors",13    "WaiNSFW V15": "https://huggingface.co/WhiteAiZ/WAI-NSFW-illustrious-SDXL-V015/resolve/main/waiNSFWIllustrious_v150.safetensors",14    "waiNSFW V14": "https://huggingface.co/Ine007/waiNSFWIllustrious_v140/resolve/main/waiNSFWIllustrious_v140.safetensors",15    "waiSHUFFLENOOB_v20": "https://huggingface.co/WhiteAiZ/waiSHUFFLENOOB_v20/resolve/main/waiSHUFFLENOOB_v20.safetensors",16    "waiSHUFFLENOOB_vpred20": "https://huggingface.co/WhiteAiZ/waiSHUFFLENOOB_v20/resolve/main/waiSHUFFLENOOB_vPred20.safetensors",17    "ntrMIXIllustriousXL_XIII": "https://huggingface.co/misri/ntrMIXIllustriousXL_xiii/resolve/main/ntrMIXIllustriousXL_xiii.safetensors",18    "NoobaiCyberFix": "https://civitaiarchive.com/api/download/models/1122850",19    "NoobaiCyberFix vpred": "https://civitaiarchive.com/api/download/models/2371102",20    "konanMix Vpred": "https://huggingface.co/wsj1995/Checkpoint/resolve/main/1365468/1542670/konanmixnoobvPredNoob_v10.safetensors",21    "Nova Anime XL": "https://huggingface.co/Chattiori/ChattioriMixesXL/resolve/main/NovaAnimeILV8.safetensors",22    "Illustrious XL personal merge": "https://huggingface.co/nnnn1111/models/resolve/main/illustriousXLPersonalMerge_v30Noob10based.safetensors",23    "Illustrious XL personal merge vpred": "https://huggingface.co/datasets/John6666/model-mirror-14/resolve/main/illustriousXLPersonalMerge_vp05testLowsteps.safetensors",24    "Ikastrious": "https://civitaiarchive.com/api/download/models/2471934",25    "Ikastrious Noobai": "https://huggingface.co/minaiosu/giko/resolve/main/ikastrious_v95.safetensors",26    "RouWei": "https://civitaiarchive.com/api/download/models/1832460",27    "RouWei Vpred": "https://huggingface.co/WhiteAiZ/RouWei/resolve/main/rouwei_v080Vpred.safetensors",28    "PlantMint Walnut": "https://huggingface.co/wsj1995/Checkpoint/resolve/main/1162518/1714002/plantMilkModelSuite_walnut.safetensors"29}30 31PRESET_VAES = {32    "sdxl_vae": "https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors",33    "sdxl_vae_fix": "https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/resolve/main/sdxl_vae.safetensors",34    "sdxl_anime_vae": "https://huggingface.co/Anzhc/Anzhcs-VAEs/resolve/main/SDXL%20Anime%20VAE%20Dec-only%20B3.safetensors",35    "sdxl_neptunia_vae": "https://huggingface.co/JustAnotherCibrarian/vae/resolve/main/1290283/1455983/neptuniaXLILNAIVAE_contrastColors.safetensors",36    "sdxl_luna_vae": "https://huggingface.co/yuu062/tameshi/resolve/main/lunaXLVAE_luna.safetensors"37}38 39PRESET_UPSCALERS = {40    "AnimeSharp": "https://huggingface.co/Kim2091/AnimeSharp/resolve/main/4x-AnimeSharp.pth",41    "UltraSharp": "https://huggingface.co/lokCX/4x-Ultrasharp/resolve/main/4x-UltraSharp.pth",42    "Remacri": "https://huggingface.co/LyliaEngine/remacri_original/resolve/main/remacri_original.pt",43    "RealESRGAN_x4plus_anime": "https://huggingface.co/gemasai/RealESRGAN_x4plus_anime_6B/resolve/main/RealESRGAN_x4plus_anime_6B.pth",44    "JaNai": "https://huggingface.co/halllooo/4x_illustrationJaNaiV1/resolve/main/4x_IllustrationJaNai_V1_ESRGAN_135k.pth",45    "YandereNeoXL": "https://huggingface.co/kaeru-shigure/mlx-4x_NMKD-YandereNeoXL_200k/resolve/main/4x_NMKD-YandereNeoXL_200k.safetensors"46}47 48PRESET_CONTROLNETS = {49    "Controlnet Union Pro Max": "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0/resolve/main/diffusion_pytorch_model_promax.safetensors",50    "Controlnet Lite (Todos)": "https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_sketch.safetensors, https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_softedge.safetensors, https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_dw_openpose.safetensors, https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_canny.safetensors, https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_depth_V2.safetensors, https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/resolve/main/bdsqlsz_controlllite_xl_lineart_anime_denoise.safetensors"51}52 53PRESET_DIFFUSION = {54    "z-image-turbo-Q4_K_M": "https://huggingface.co/unsloth/Z-Image-Turbo-GGUF/resolve/main/z-image-turbo-Q4_K_M.gguf",55    "z-image-turbo-Q8_0": "https://huggingface.co/unsloth/Z-Image-Turbo-GGUF/resolve/main/z-image-turbo-Q8_0.gguf"56}57 58PRESET_TEXT_ENCODERS = {59    "Qwen3-4B-Q4_K_M": "https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf",60    "Qwen3-4B-Q8_0": "https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q8_0.gguf"61}62 63PRESET_UNET = {} 64PRESET_CLIP = {} 65 66def iniciar():67    # --- Configuración de Rutas para Lightning.ai ---68    BASE_MODELS_DIR = "/tmp/models"69    LORA_DIR = "/tmp/lora"70    VAE_DIR = "/teamspace/studios/this_studio/SwarmUI/Models/VAE"71    UPSCALER_DIR = "/teamspace/studios/this_studio/SwarmUI/Models/upscale_models"72    CONTROLNET_DIR = "/tmp/controlnet"73    DIFFUSION_DIR = "/tmp/diffusion_models"74    TEXT_ENCODER_DIR = "/tmp/text_encoders"75    UNET_DIR = "/tmp/unet"76    CLIP_DIR = "/teamspace/studios/this_studio/SwarmUI/Models/clip"77    COMFY_EXT_DIR = "/teamspace/studios/this_studio/SwarmUI/dlbackend/ComfyUI/custom_nodes"78 79    # Asegurar que las carpetas existan80    os.makedirs(BASE_MODELS_DIR, exist_ok=True)81    os.makedirs(LORA_DIR, exist_ok=True)82    os.makedirs(VAE_DIR, exist_ok=True)83    os.makedirs(UPSCALER_DIR, exist_ok=True)84    os.makedirs(CONTROLNET_DIR, exist_ok=True)85    os.makedirs(DIFFUSION_DIR, exist_ok=True)86    os.makedirs(TEXT_ENCODER_DIR, exist_ok=True)87    os.makedirs(UNET_DIR, exist_ok=True)88    os.makedirs(CLIP_DIR, exist_ok=True)89    os.makedirs(COMFY_EXT_DIR, exist_ok=True)90 91    # --- Leer token desde pickle en Lightning.ai ---92    TOKEN_FILE = Path.home() / ".civitai_token.pkl"93    token_guardado = ""94    if TOKEN_FILE.exists():95        try:96            token_guardado = pickle.loads(TOKEN_FILE.read_bytes())97        except Exception:98            pass99 100    # --- Elementos de la Interfaz ---101    out_console = widgets.Output(layout={'border': '1px solid #ccc', 'padding': '10px', 'margin': '10px 0', 'height': '250px', 'overflow': 'auto'})102 103    token_input = widgets.Password(104        value=token_guardado,105        placeholder='Ingresa tu API Token (Opcional pero recomendado)',106        description='Civitai Token:',107        style={'description_width': 'initial'},108        layout=widgets.Layout(width='500px')109    )110 111    # Widgets de Progreso112    progress_bar = widgets.IntProgress(113        value=0, min=0, max=100, 114        description='Progreso:', 115        bar_style='info', 116        orientation='horizontal', 117        layout=widgets.Layout(width='80%', display='none')118    )119    status_label = widgets.Label(value="", layout=widgets.Layout(display='none'))120    progress_container = widgets.VBox([progress_bar, status_label])121 122    def ejecutar_con_progreso(cmd, is_gdown=False):123        progress_bar.value = 0124        progress_bar.layout.display = 'flex'125        status_label.layout.display = 'flex'126        status_label.value = "Iniciando descarga..."127        128        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True)129        130        for line in process.stdout:131            if not is_gdown:132                match_pct = re.search(r'\((\d+)%\)', line)133                if match_pct:134                    progress_bar.value = int(match_pct.group(1))135                136                match_speed = re.search(r'DL:([^\s]+)', line)137                if match_speed:138                    status_label.value = f"Descargando... Velocidad: {match_speed.group(1)}"139            else:140                match_pct = re.search(r'(\d{1,3})%', line)141                if match_pct:142                    progress_bar.value = int(match_pct.group(1))143                    status_label.value = "Descargando con gdown..."144 145        process.wait()146        progress_bar.value = 100147        progress_bar.bar_style = 'success'148        status_label.value = "¡Descarga de este archivo completada! ✅"149 150    def procesar_descarga(b, url_widget, method_widget, dest_path):151        raw_urls = url_widget.value.strip()152        method = method_widget.value153        token = token_input.value.strip()154        155        urls = [u.strip() for u in raw_urls.split(",") if u.strip()]156        157        if not urls:158            with out_console:159                clear_output()160                display(HTML("<h4 style='color:red;'>⚠️ Por favor, ingresa al menos una URL válida.</h4>"))161            return162 163        with out_console:164            clear_output()165            display(HTML(f"<h2>🚀 Iniciando cola de descarga: {len(urls)} archivo(s)</h2>"))166 167        for i, url in enumerate(urls, 1):168            progress_bar.bar_style = 'info'169            progress_bar.value = 0170            status_label.value = f"Preparando archivo {i} de {len(urls)}..."171 172            download_url = url173            if ("civitai.com" in url or "civitaiarchive.com" in url) and token:174                download_url = f"{url}{'&' if '?' in url else '?'}token={token}"175 176            pretty_name = "Desconocido"177            178            if url == "https://huggingface.co/xinsir/controlnet-union-sdxl-1.0/resolve/main/diffusion_pytorch_model_promax.safetensors":179                pretty_name = "Controlnet_Union_Pro_Max.safetensors"180            elif method == 'aria2':181                if "civitai.com" in url or "civitaiarchive.com" in url:182                    try:183                        with requests.get(download_url, stream=True, timeout=5) as r:184                            cd = r.headers.get('Content-Disposition', '')185                            match = re.findall(r'filename[*]?=(?:UTF-8\'\')?["\']?([^"\';]+)["\']?', cd)186                            if match: 187                                pretty_name = match[0]188                            else:189                                pretty_name = url.split('/')[-1].split('?')[0]190                    except Exception:191                        pretty_name = url.split('/')[-1].split('?')[0]192                else:193                    pretty_name = url.split('/')[-1].split('?')[0]194            elif method == 'gdown':195                pretty_name = "Archivo de Google Drive (Gdown gestiona el nombre)"196 197            with out_console:198                display(HTML(f"<hr><h3 style='color:#D4AF37;'>📥 [{i}/{len(urls)}] Descargando: <code>{pretty_name}</code></h3>"))199                display(HTML(f"<h4 style='color:#4682B4;'>📁 Destino: <code>{dest_path}</code></h4>"))200                201                if ("civitai.com" in url or "civitaiarchive.com" in url) and token:202                    print("🔑 Token detectado y aplicado desde el gestor.")203                elif ("civitai.com" in url or "civitaiarchive.com" in url) and not token:204                    print("⚠️ Descargando sin token (algunos modelos pueden requerirlo).")205 206            if method == 'aria2':207                cmd = [208                    "aria2c", "--content-disposition",209                    "-c", "-x", "16", "-s", "16", "-k", "1M",210                    "--summary-interval=1", "-d", dest_path211                ]212                213                if pretty_name and pretty_name != "Desconocido" and "civitai.com" not in url and "civitaiarchive.com" not in url:214                    cmd.extend(["-o", pretty_name, url])215                else:216                    cmd.append(download_url)217                    218                ejecutar_con_progreso(cmd, is_gdown=False)219                220                if "huggingface.co" in url and pretty_name:221                    for f in os.listdir(dest_path):222                        if re.fullmatch(r'[0-9a-f]{64}', f):223                            os.rename(os.path.join(dest_path, f), os.path.join(dest_path, pretty_name))224                            break225 226            elif method == 'gdown':227                cmd = ["gdown", "--fuzzy", url, "-O", f"{dest_path}/"]228                ejecutar_con_progreso(cmd, is_gdown=True)229 230        with out_console:231            display(HTML("<hr><h2 style='color:lightgreen;'>✅ ¡Todas las descargas han finalizado!</h2>"))232 233    def procesar_extensiones(b, url_widget):234        raw_urls = url_widget.value.strip()235        urls = [u.strip() for u in raw_urls.split(",") if u.strip()]236        237        if not urls:238            with out_console:239                clear_output()240                display(HTML("<h4 style='color:red;'>⚠️ Por favor, ingresa al menos una URL de GitHub válida.</h4>"))241            return242 243        with out_console:244            clear_output()245            display(HTML(f"<h2>🚀 Iniciando clonación de {len(urls)} extensión(es)</h2>"))246 247        for i, url in enumerate(urls, 1):248            progress_bar.layout.display = 'flex'249            status_label.layout.display = 'flex'250            progress_bar.bar_style = 'info'251            progress_bar.value = 50 252            253            repo_name = url.rstrip('/').split('/')[-1].replace('.git', '')254            target_path = os.path.join(COMFY_EXT_DIR, repo_name)255            status_label.value = f"Clonando {repo_name} ({i}/{len(urls)})..."256 257            with out_console:258                display(HTML(f"<hr><h3 style='color:#D4AF37;'>📥 [{i}/{len(urls)}] Clonando: <code>{repo_name}</code></h3>"))259                display(HTML(f"<h4 style='color:#4682B4;'>📁 Destino: <code>{target_path}</code></h4>"))260 261            if os.path.exists(target_path):262                with out_console:263                    print(f"⚠️ La carpeta {repo_name} ya existe. Saltando git clone...")264            else:265                cmd_clone = ["git", "clone", url, target_path]266                res_clone = subprocess.run(cmd_clone, capture_output=True, text=True)267                if res_clone.returncode != 0:268                    with out_console:269                        print(f"❌ Error al clonar:\n{res_clone.stderr}")270                    continue271 272            req_file = os.path.join(target_path, "requirements.txt")273            if os.path.exists(req_file):274                status_label.value = f"Instalando dependencias para {repo_name}..."275                with out_console:276                    print(f"📦 requirements.txt detectado. Instalando con uv...")277                278                cmd_uv = ["uv", "pip", "install", "--system", "-r", req_file]279                res_uv = subprocess.run(cmd_uv, capture_output=True, text=True)280                281                with out_console:282                    if res_uv.returncode == 0:283                        print("✅ Dependencias instaladas exitosamente.")284                    else:285                        print(f"⚠️ Hubo un problema al instalar algunas dependencias:\n{res_uv.stderr}")286            else:287                with out_console:288                    print("ℹ️ No se encontró requirements.txt. Omitiendo instalación de dependencias.")289 290            progress_bar.value = 100291            progress_bar.bar_style = 'success'292            status_label.value = "¡Extensión procesada! ✅"293 294        with out_console:295            display(HTML("<hr><h2 style='color:lightgreen;'>✅ ¡Todas las extensiones han sido procesadas!</h2>"))296 297 298    # --- Pestaña 1: Modelos Base ---299    opciones_presets_base = ['-- Personalizado / Manual --'] + list(PRESET_MODELS.keys())300    preset_base_dropdown = widgets.Dropdown(options=opciones_presets_base, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))301    base_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))302 303    def actualizar_url_base(change):304        seleccion = change['new']305        if seleccion in PRESET_MODELS:306            base_url.value = PRESET_MODELS[seleccion]307        else:308            base_url.value = ""309 310    preset_base_dropdown.observe(actualizar_url_base, names='value')311 312    base_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')313    base_btn = widgets.Button(description='Descargar Modelos', button_style='primary', icon='download')314    base_btn.on_click(lambda b: procesar_descarga(b, base_url, base_method, BASE_MODELS_DIR))315 316    tab_base = widgets.VBox([317        widgets.HTML("<h4>Descargar Modelos (Checkpoints)</h4><p>Elige un modelo favorito o pega enlaces separados por comas (,)</p>"),318        preset_base_dropdown,319        widgets.HBox([base_url, base_method]),320        base_btn321    ])322 323    # --- Pestaña 2: LoRAs ---324    lora_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URLs:', layout=widgets.Layout(width='80%'))325    lora_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')326    lora_btn = widgets.Button(description='Descargar LoRAs', button_style='success', icon='download')327    lora_btn.on_click(lambda b: procesar_descarga(b, lora_url, lora_method, LORA_DIR))328 329    tab_lora = widgets.VBox([330        widgets.HTML("<h4>Descargar LoRAs</h4><p>Pega múltiples URLs separadas por comas (,)</p>"),331        widgets.HBox([lora_url, lora_method]),332        lora_btn333    ])334 335    # --- Pestaña 3: VAEs ---336    opciones_presets_vae = ['-- Personalizado / Manual --'] + list(PRESET_VAES.keys())337    preset_vae_dropdown = widgets.Dropdown(options=opciones_presets_vae, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))338    vae_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))339 340    def actualizar_url_vae(change):341        seleccion = change['new']342        if seleccion in PRESET_VAES:343            vae_url.value = PRESET_VAES[seleccion]344        else:345            vae_url.value = ""346 347    preset_vae_dropdown.observe(actualizar_url_vae, names='value')348 349    vae_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')350    vae_btn = widgets.Button(description='Descargar VAEs', button_style='info', icon='download')351    vae_btn.on_click(lambda b: procesar_descarga(b, vae_url, vae_method, VAE_DIR))352 353    tab_vae = widgets.VBox([354        widgets.HTML("<h4>Descargar VAEs</h4><p>Elige un VAE favorito o pega enlaces separados por comas (,)</p>"),355        preset_vae_dropdown,356        widgets.HBox([vae_url, vae_method]),357        vae_btn358    ])359 360    # --- Pestaña 4: Upscalers ---361    opciones_presets_upscalers = ['-- Personalizado / Manual --', '-- Descargar Todos --'] + list(PRESET_UPSCALERS.keys())362    preset_upscaler_dropdown = widgets.Dropdown(options=opciones_presets_upscalers, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))363    upscaler_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))364 365    def actualizar_url_upscaler(change):366        seleccion = change['new']367        if seleccion == '-- Descargar Todos --':368            upscaler_url.value = ", ".join(PRESET_UPSCALERS.values())369        elif seleccion in PRESET_UPSCALERS:370            upscaler_url.value = PRESET_UPSCALERS[seleccion]371        else:372            upscaler_url.value = ""373 374    preset_upscaler_dropdown.observe(actualizar_url_upscaler, names='value')375 376    upscaler_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')377    upscaler_btn = widgets.Button(description='Descargar Upscalers', button_style='warning', icon='download')378    upscaler_btn.on_click(lambda b: procesar_descarga(b, upscaler_url, upscaler_method, UPSCALER_DIR))379 380    tab_upscaler = widgets.VBox([381        widgets.HTML("<h4>Descargar Upscalers (ESRGAN)</h4><p>Elige un upscaler, selecciona <b>'-- Descargar Todos --'</b>, o pega enlaces separados por comas (,)</p>"),382        preset_upscaler_dropdown,383        widgets.HBox([upscaler_url, upscaler_method]),384        upscaler_btn385    ])386 387    # --- Pestaña 5: ControlNet ---388    opciones_presets_controlnet = ['-- Personalizado / Manual --'] + list(PRESET_CONTROLNETS.keys())389    preset_controlnet_dropdown = widgets.Dropdown(options=opciones_presets_controlnet, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))390    controlnet_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))391 392    def actualizar_url_controlnet(change):393        seleccion = change['new']394        if seleccion in PRESET_CONTROLNETS:395            controlnet_url.value = PRESET_CONTROLNETS[seleccion]396        else:397            controlnet_url.value = ""398 399    preset_controlnet_dropdown.observe(actualizar_url_controlnet, names='value')400 401    controlnet_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')402    controlnet_btn = widgets.Button(description='Descargar ControlNet', button_style='danger', icon='download')403    controlnet_btn.on_click(lambda b: procesar_descarga(b, controlnet_url, controlnet_method, CONTROLNET_DIR))404 405    tab_controlnet = widgets.VBox([406        widgets.HTML("<h4>Descargar Modelos ControlNet</h4><p>Elige el Pro Max, el paquete Lite, o pega enlaces separados por comas (,)</p>"),407        preset_controlnet_dropdown,408        widgets.HBox([controlnet_url, controlnet_method]),409        controlnet_btn410    ])411 412    # --- Pestaña 6: Diffusion Models ---413    opciones_presets_diffusion = ['-- Personalizado / Manual --'] + list(PRESET_DIFFUSION.keys())414    preset_diffusion_dropdown = widgets.Dropdown(options=opciones_presets_diffusion, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))415    diffusion_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))416 417    def actualizar_url_diffusion(change):418        seleccion = change['new']419        if seleccion in PRESET_DIFFUSION:420            diffusion_url.value = PRESET_DIFFUSION[seleccion]421        else:422            diffusion_url.value = ""423 424    preset_diffusion_dropdown.observe(actualizar_url_diffusion, names='value')425 426    diffusion_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')427    diffusion_btn = widgets.Button(description='Descargar Diffusion Models', button_style='primary', icon='download')428    diffusion_btn.on_click(lambda b: procesar_descarga(b, diffusion_url, diffusion_method, DIFFUSION_DIR))429 430    tab_diffusion = widgets.VBox([431        widgets.HTML("<h4>Descargar Diffusion Models</h4><p>Elige un modelo GGUF o pega enlaces separados por comas (,)</p>"),432        preset_diffusion_dropdown,433        widgets.HBox([diffusion_url, diffusion_method]),434        diffusion_btn435    ])436 437    # --- Pestaña 7: Text Encoders ---438    opciones_presets_te = ['-- Personalizado / Manual --'] + list(PRESET_TEXT_ENCODERS.keys())439    preset_te_dropdown = widgets.Dropdown(options=opciones_presets_te, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))440    te_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))441 442    def actualizar_url_te(change):443        seleccion = change['new']444        if seleccion in PRESET_TEXT_ENCODERS:445            te_url.value = PRESET_TEXT_ENCODERS[seleccion]446        else:447            te_url.value = ""448 449    preset_te_dropdown.observe(actualizar_url_te, names='value')450 451    te_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')452    te_btn = widgets.Button(description='Descargar Text Encoders', button_style='info', icon='download')453    te_btn.on_click(lambda b: procesar_descarga(b, te_url, te_method, TEXT_ENCODER_DIR))454 455    tab_te = widgets.VBox([456        widgets.HTML("<h4>Descargar Text Encoders</h4><p>Elige un modelo Qwen o pega enlaces separados por comas (,)</p>"),457        preset_te_dropdown,458        widgets.HBox([te_url, te_method]),459        te_btn460    ])461 462    # --- Pestaña 8: Unet ---463    opciones_presets_unet = ['-- Personalizado / Manual --'] + list(PRESET_UNET.keys())464    preset_unet_dropdown = widgets.Dropdown(options=opciones_presets_unet, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))465    unet_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))466 467    def actualizar_url_unet(change):468        seleccion = change['new']469        if seleccion in PRESET_UNET:470            unet_url.value = PRESET_UNET[seleccion]471        else:472            unet_url.value = ""473 474    preset_unet_dropdown.observe(actualizar_url_unet, names='value')475 476    unet_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')477    unet_btn = widgets.Button(description='Descargar Unet', button_style='primary', icon='download')478    unet_btn.on_click(lambda b: procesar_descarga(b, unet_url, unet_method, UNET_DIR))479 480    tab_unet = widgets.VBox([481        widgets.HTML("<h4>Descargar Modelos Unet</h4><p>Pega múltiples URLs separadas por comas (,)</p>"),482        preset_unet_dropdown,483        widgets.HBox([unet_url, unet_method]),484        unet_btn485    ])486 487    # --- Pestaña 9: Clip ---488    opciones_presets_clip = ['-- Personalizado / Manual --'] + list(PRESET_CLIP.keys())489    preset_clip_dropdown = widgets.Dropdown(options=opciones_presets_clip, value='-- Personalizado / Manual --', description='Favoritos:', layout=widgets.Layout(width='80%'))490    clip_url = widgets.Text(placeholder='https://url1, https://url2, ...', description='URL:', layout=widgets.Layout(width='80%'))491 492    def actualizar_url_clip(change):493        seleccion = change['new']494        if seleccion in PRESET_CLIP:495            clip_url.value = PRESET_CLIP[seleccion]496        else:497            clip_url.value = ""498 499    preset_clip_dropdown.observe(actualizar_url_clip, names='value')500 501    clip_method = widgets.Dropdown(options=['aria2', 'gdown'], value='aria2', description='Método:')502    clip_btn = widgets.Button(description='Descargar Clip', button_style='info', icon='download')503    clip_btn.on_click(lambda b: procesar_descarga(b, clip_url, clip_method, CLIP_DIR))504 505    tab_clip = widgets.VBox([506        widgets.HTML("<h4>Descargar Modelos Clip</h4><p>Pega múltiples URLs separadas por comas (,)</p>"),507        preset_clip_dropdown,508        widgets.HBox([clip_url, clip_method]),509        clip_btn510    ])511 512    # --- Pestaña 10: Extensiones ComfyUI ---513    comfy_ext_url = widgets.Text(placeholder='https://github.com/autor/repositorio.git, ...', description='URLs (Git):', layout=widgets.Layout(width='80%'))514    comfy_ext_btn = widgets.Button(description='Clonar e Instalar', button_style='danger', icon='code')515    comfy_ext_btn.on_click(lambda b: procesar_extensiones(b, comfy_ext_url))516 517    tab_comfy_ext = widgets.VBox([518        widgets.HTML("<h4>Clonar Extensiones para ComfyUI</h4><p>Pega enlaces de repositorios de GitHub separados por comas (,). Se clonarán e instalarán sus requirements usando uv.</p>"),519        comfy_ext_url,520        comfy_ext_btn521    ])522 523    # --- Ensamblar Interfaz ---524    tabs = widgets.Tab(children=[tab_base, tab_lora, tab_vae, tab_upscaler, tab_controlnet, tab_diffusion, tab_te, tab_unet, tab_clip, tab_comfy_ext])525    tabs.set_title(0, '📦 Modelos Base')526    tabs.set_title(1, '✨ LoRAs')527    tabs.set_title(2, '🎨 VAEs')528    tabs.set_title(3, '🔍 Upscalers')529    tabs.set_title(4, '🕹️ ControlNet')530    tabs.set_title(5, '🌌 Diffusion Models')531    tabs.set_title(6, '📝 Text Encoders')532    tabs.set_title(7, '🧠 Unet')533    tabs.set_title(8, '🔗 Clip')534    tabs.set_title(9, '🧩 Extensiones Comfy')535 536    if token_guardado:537        mensaje_token = "<i>(✅ Token cargado automáticamente desde la memoria)</i>"538    else:539        mensaje_token = "<i>(⚠️ No se encontró token guardado, puedes ingresarlo manualmente)</i>"540 541    ui = widgets.VBox([542        widgets.HTML("<h2>Gestor Total de Descargas para SwarmUI (Lightning.ai)</h2>"),543        widgets.HBox([token_input, widgets.HTML(mensaje_token)]),544        tabs,545        widgets.HTML("<hr>"),546        progress_container,547        out_console548    ])549 550    display(ui)