酒馆文生图插件

使用sd、nai或comfyui 配合ai在酒馆生成插图,支持动态加载模型/采样器/调度器/Hires修复算法/Lora,并为NAI添加Vibe Transfer功能,为ComfyUI添加工作流管理和动态节点连接。新增标签搜索功能。1.8增加sd一致化。

// ==UserScript==
// @name         酒馆文生图插件
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  使用sd、nai或comfyui 配合ai在酒馆生成插图,支持动态加载模型/采样器/调度器/Hires修复算法/Lora,并为NAI添加Vibe Transfer功能,为ComfyUI添加工作流管理和动态节点连接。新增标签搜索功能。1.8增加sd一致化。
// @author       茶蘼
// @grant        unsafeWindow
// @match        *://*/*
// @require      https://code.jquery.com/jquery-3.4.1.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @grant        GM_xmlhttpRequest
// @connect      127.0.0.1
// @connect      novelai.net
// @connect      vagrantup.com
// @connect      danbooru.donmai.us
// @connect      *
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// ==/UserScript==

( function() {
    'use strict';
    let ster="";
    let globalImageCounter = 0;
    let locationToImageIdMap = {};
    let db=""
    let objectStorereadwrite="";
    let  objectStorereadonly="";
    let  xiancheng=true;
    let isAutoClicking = false;

    $(document).ready(async function() {
        // 1. 等待缓存初始化完成
        await initializeCache();

        // 2. 缓存加载完毕后,再执行所有UI和定时器设置
        console.log("文生图插件:缓存已就绪,开始初始化UI和监听器...");

        ster = setInterval(addNewElement, 2000);
        const style1 = document.createElement('style');
        style1.textContent = `
      .button_image {
      /* 基础样式 */
      padding: 4px 8px;
      font-size: 13px;
      font-weight: 600;
      color: #c0caf5;
      background: #292e42;
      border: 1px solid #414868;
      border-radius: 5px;
      cursor: pointer;
      transition: all 0.2s ease;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      /* 文本和图标布局 */
      display: inline-flex;
      align-items: center;
      gap: 8px;
      /* 防止文本换行 */
      white-space: nowrap;
      /* 去除默认按钮样式 */
      outline: none;
      -webkit-appearance: none;
      -moz-appearance: none;
       }
       .button_image:hover {
         background: #3b4261;
         color: #fff;
       }
        `;
      document.head.appendChild(style1);
      const setupObserver = () => {
          const sendButton = document.getElementById('send_but');
          const stopButton = document.getElementById('mes_stop');

          if (sendButton && stopButton) {
              const observer = new MutationObserver((mutations) => {
                  // 使用 requestIdleCallback 或 setTimeout(0) 避免阻塞主线程
                  requestIdleCallback(() => {
                      replaceSpansWithImagesst();
                  });
              });

              const config = { attributes: true, attributeFilter: ['style'] };

              observer.observe(sendButton, config);
              observer.observe(stopButton, config);

              console.log("文生图插件:已附加状态变化观察器,实现即时响应。");
          } else {
              // 如果按钮还没找到,1秒后重试
              setTimeout(setupObserver, 1000);
          }
      };

        // 启动观察器设置
        setupObserver();

        // 将主循环也放在这里,确保它在缓存加载后启动
        setInterval(replaceSpansWithImagesst, 2000);

        // 页面加载完成后,立即执行一次检查,以处理已存在的标签
        replaceSpansWithImagesst();
    });

    const dbName = 'image';
    const dbVersion = 1;
    const objectStoreName = 'Image_caching';

    function getDB() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(dbName, dbVersion);
            request.onerror = event => reject(`Database error: ${event.target.error}`);
            request.onsuccess = event => resolve(event.target.result);
            request.onupgradeneeded = event => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains(objectStoreName)) {
                    db.createObjectStore(objectStoreName, { keyPath: 'id' });
                }
            };
        });
    }

    async function Storereadwrite(data) {
        const db = await getDB();
        return new Promise((resolve, reject) => {
            const transaction = db.transaction([objectStoreName], 'readwrite');
            const store = transaction.objectStore(objectStoreName);
            const request = store.put(data);
            request.onsuccess = () => resolve();
            request.onerror = event => reject(`Write error: ${event.target.error}`);
            transaction.oncomplete = () => db.close();
        });
    }

    async function Storereadonly(id) {
        const db = await getDB();
        return new Promise((resolve, reject) => {
            const transaction = db.transaction([objectStoreName], 'readonly');
            const store = transaction.objectStore(objectStoreName);
            const request = store.get(id);
            request.onsuccess = event => resolve(event.target.result);
            request.onerror = event => reject(`Read error: ${event.target.error}`);
            transaction.oncomplete = () => db.close();
        });
    }

    async function StoreGetAll() {
        const db = await getDB();
        return new Promise((resolve, reject) => {
            const transaction = db.transaction([objectStoreName], 'readonly');
            const store = transaction.objectStore(objectStoreName);
            const request = store.getAll();
            request.onsuccess = event => resolve(event.target.result || []);
            request.onerror = event => reject(`GetAll error: ${event.target.error}`);
            transaction.oncomplete = () => db.close();
        });
    }


    async function Storedelete(id) {
        const db = await getDB();
        return new Promise((resolve, reject) => {
            const transaction = db.transaction([objectStoreName], 'readwrite');
            const store = transaction.objectStore(objectStoreName);
            const request = store.delete(id);
            request.onsuccess = () => resolve();
            request.onerror = event => reject(`Delete error: ${event.target.error}`);
            transaction.oncomplete = () => db.close();
        });
    }

    async function StoreClear() {
        const db = await getDB();
        return new Promise((resolve, reject) => {
            const transaction = db.transaction([objectStoreName], 'readwrite');
            const store = transaction.objectStore(objectStoreName);
            const request = store.clear();
            request.onsuccess = () => resolve();
            request.onerror = event => reject(`Clear error: ${event.target.error}`);
            transaction.oncomplete = () => db.close();
        });
    }

    async function getNextImageId() {
        if (globalImageCounter === 0) {
            globalImageCounter = await GM_getValue('globalImageCounter', 0);
        }
        globalImageCounter++;
        await GM_setValue('globalImageCounter', globalImageCounter);
        return globalImageCounter;
    }
    //
    const defaultComfyWorkflow = {
      3: {
        "inputs": {
          "filename_prefix": "ComfyUI",
          "images": [ "14", 0 ]
        },
        "class_type": "SaveImage",
        "_meta": { "title": "保存图像" }
      },
      4: {
        "inputs": {
          "model_name": "sam_vit_b_01ec64.pth",
          "device_mode": "AUTO"
        },
        "class_type": "SAMLoader",
        "_meta": { "title": "SAMLoader (Impact)" }
      },
      5: {
        "inputs": {
          "model_name": "bbox/face_yolov8s.pt"
        },
        "class_type": "UltralyticsDetectorProvider",
        "_meta": { "title": "UltralyticsDetectorProvider" }
      },
      6: {
        "inputs": {
          "lora_01": "None", "strength_01": 1, "lora_02": "None", "strength_02": 1,
          "lora_03": "None", "strength_03": 1, "lora_04": "None", "strength_04": 1,
          "model": [ "10", 0 ],
          "clip": [ "10", 1 ]
        },
        "class_type": "Lora Loader Stack (rgthree)",
        "_meta": { "title": "Lora Loader Stack (rgthree)" }
      },
      8: {
        "inputs": {
          "text": "positive prompt",
          "clip": [ "6", 1 ]
        },
        "class_type": "CLIPTextEncode",
        "_meta": { "title": "正面提示词" }
      },
      9: {
        "inputs": {
          "text": "negative prompt",
          "clip": [ "10", 1 ]
        },
        "class_type": "CLIPTextEncode",
        "_meta": { "title": "负面提示词" }
      },
      10: {
        "inputs": {
          "ckpt_name": "anything-v5-PrtRE.safetensors"
        },
        "class_type": "CheckpointLoaderSimple",
        "_meta": { "title": "主模型 (Checkpoint)" }
      },
      12: {
        "inputs": {
          "width": 832, "height": 1216, "batch_size": 1
        },
        "class_type": "EmptyLatentImage",
        "_meta": { "title": "分辨率 (Empty Latent)" }
      },
      13: {
        "inputs": {
          "seed": 466166474766921, "steps": 28, "cfg": 7, "sampler_name": "euler_ancestral",
          "scheduler": "karras", "denoise": 1,
          "model": [ "6", 0 ],
          "positive": [ "8", 0 ],
          "negative": [ "9", 0 ],
          "latent_image": [ "12", 0 ]
        },
        "class_type": "KSampler",
        "_meta": { "title": "采样器 (KSampler)" }
      },
      14: {
        "inputs": {
          "guide_size": 512, "guide_size_for": true, "max_size": 1024, "seed": 780524262703529,
          "steps": 12, "cfg": 4.5, "sampler_name": "euler_ancestral", "scheduler": "karras", "denoise": 0.5,
          "feather": 5, "noise_mask": true, "force_inpaint": true, "bbox_threshold": 0.5, "bbox_dilation": 10,
          "bbox_crop_factor": 3, "sam_detection_hint": "center-1", "sam_dilation": 0, "sam_threshold": 0.93,
          "sam_bbox_expansion": 0, "sam_mask_hint_threshold": 0.7, "sam_mask_hint_use_negative": "False", "drop_size": 10,
          "wildcard": "", "cycle": 1, "inpaint_model": false, "noise_mask_feather": 20, "tiled_encode": false, "tiled_decode": false,
          "image": [ "15", 0 ],
          "model": [ "10", 0 ],
          "clip": [ "10", 1 ],
          "vae": [ "10", 2 ],
          "positive": [ "8", 0 ],
          "negative": [ "9", 0 ],
          "bbox_detector": [ "5", 0 ],
          "sam_model_opt": [ "4", 0 ]
        },
        "class_type": "FaceDetailer",
        "_meta": { "title": "FaceDetailer" }
      },
      15: {
        "inputs": {
          "samples": [ "13", 0 ],
          "vae": [ "10", 2 ]
        },
        "class_type": "VAEDecode",
        "_meta": { "title": "VAE解码" }
      }
    };
    const simpleWorkflow = {
          3: { "inputs": { "filename_prefix": "ComfyUI", "images": ["15", 0] }, "class_type": "SaveImage", "_meta": { "title": "保存图像" } },
          6: { "inputs": { "lora_01": "None", "strength_01": 1, "lora_02": "None", "strength_02": 1, "lora_03": "None", "strength_03": 1, "lora_04": "None", "strength_04": 1, "model": ["10", 0], "clip": ["10", 1] }, "class_type": "Lora Loader Stack (rgthree)", "_meta": { "title": "Lora Loader Stack (rgthree)" } },
          8: { "inputs": { "text": "", "clip": ["6", 1] }, "class_type": "CLIPTextEncode", "_meta": { "title": "正面提示词" } },
          9: { "inputs": { "text": "", "clip": ["10", 1] }, "class_type": "CLIPTextEncode", "_meta": { "title": "负面提示词" } },
          10: { "inputs": { "ckpt_name": "日系二次元.safetensors" }, "class_type": "CheckpointLoaderSimple", "_meta": { "title": "主模型 (Checkpoint)" } },
          12: { "inputs": { "width": 832, "height": 1216, "batch_size": 1 }, "class_type": "EmptyLatentImage", "_meta": { "title": "分辨率 (Empty Latent)" } },
          13: { "inputs": { "seed": 466166474766921, "steps": 22, "cfg": 5.5, "sampler_name": "euler_ancestral", "scheduler": "karras", "denoise": 1, "model": ["6", 0], "positive": ["8", 0], "negative": ["9", 0], "latent_image": ["12", 0] }, "class_type": "KSampler", "_meta": { "title": "采样器 (KSampler)" } },
          15: { "inputs": { "samples": ["13", 0], "vae": ["10", 2] }, "class_type": "VAEDecode", "_meta": { "title": "VAE解码" } }
    };
    const hiresWorkflow = {
      3: { "inputs": { "filename_prefix": "ComfyUI", "images": [ "17", 0 ] }, "class_type": "SaveImage", "_meta": { "title": "保存图像" } },
      4: { "inputs": { "model_name": "sam_vit_b_01ec64.pth", "device_mode": "AUTO" }, "class_type": "SAMLoader", "_meta": { "title": "SAMLoader (Impact)" } },
      5: { "inputs": { "model_name": "bbox/face_yolov8s.pt" }, "class_type": "UltralyticsDetectorProvider", "_meta": { "title": "UltralyticsDetectorProvider" } },
      6: { "inputs": { "lora_01": "None", "strength_01": 1, "lora_02": "None", "strength_02": 1, "lora_03": "None", "strength_03": 1, "lora_04": "None", "strength_04": 1, "model": [ "10", 0 ], "clip": [ "10", 1 ] }, "class_type": "Lora Loader Stack (rgthree)", "_meta": { "title": "Lora Loader Stack (rgthree)" } },
      7: { "inputs": { "samples": [ "16", 0 ], "vae": [ "10", 2 ] }, "class_type": "VAEDecode", "_meta": { "title": "VAE解码" } },
      8: { "inputs": { "text": "", "clip": [ "6", 1 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "正面提示词" } },
      9: { "inputs": { "text": "", "clip": [ "10", 1 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "负面提示词" } },
      10: { "inputs": { "ckpt_name": "日系二次元.safetensors" }, "class_type": "CheckpointLoaderSimple", "_meta": { "title": "Checkpoint加载器(简易)" } },
      13: { "inputs": { "upscale_by": 1.3, "seed": 1125258729553273, "steps": 15, "cfg": 5, "sampler_name": "euler_ancestral", "scheduler": "karras", "denoise": 0.2, "mode_type": "Linear", "tile_width": 512, "tile_height": 512, "mask_blur": 8, "tile_padding": 32, "seam_fix_mode": "None", "seam_fix_denoise": 1, "seam_fix_width": 64, "seam_fix_mask_blur": 8, "seam_fix_padding": 16, "force_uniform_tiles": true, "tiled_decode": false, "image": [ "7", 0 ], "model": [ "6", 0 ], "positive": [ "8", 0 ], "negative": [ "9", 0 ], "vae": [ "10", 2 ], "upscale_model": [ "14", 0 ] }, "class_type": "UltimateSDUpscale", "_meta": { "title": "Ultimate SD Upscale" } },
      14: { "inputs": { "model_name": "RealESRGAN_x4plus_anime_6B.pth" }, "class_type": "UpscaleModelLoader", "_meta": { "title": "加载放大模型" } },
      15: { "inputs": { "width": 832, "height": 1216, "batch_size": 1 }, "class_type": "EmptyLatentImage", "_meta": { "title": "空Latent图像" } },
      16: { "inputs": { "seed": 466166474766921, "steps": 22, "cfg": 5.5, "sampler_name": "euler_ancestral", "scheduler": "karras", "denoise": 1, "model": [ "6", 0 ], "positive": [ "8", 0 ], "negative": [ "9", 0 ], "latent_image": [ "15", 0 ] }, "class_type": "KSampler", "_meta": { "title": "K采样器" } },
      17: { "inputs": { "guide_size": 512, "guide_size_for": true, "max_size": 1024, "seed": 329726187282802, "steps": 12, "cfg": 4.5, "sampler_name": "euler_ancestral", "scheduler": "karras", "denoise": 0.5, "feather": 5, "noise_mask": true, "force_inpaint": true, "bbox_threshold": 0.5, "bbox_dilation": 10, "bbox_crop_factor": 3, "sam_detection_hint": "center-1", "sam_dilation": 0, "sam_threshold": 0.93, "sam_bbox_expansion": 0, "sam_mask_hint_threshold": 0.7, "sam_mask_hint_use_negative": "False", "drop_size": 10, "wildcard": "", "cycle": 1, "inpaint_model": false, "noise_mask_feather": 20, "tiled_encode": false, "tiled_decode": false, "image": [ "13", 0 ], "model": [ "10", 0 ], "clip": [ "10", 1 ], "vae": [ "10", 2 ], "positive": [ "8", 0 ], "negative": [ "9", 0 ], "bbox_detector": [ "5", 0 ], "sam_model_opt": [ "4", 0 ] }, "class_type": "FaceDetailer", "_meta": { "title": "FaceDetailer" } }
    };


    const defaultSettings = {
        scriptEnabled:false,
        cache: 1,
        startTag: 'image###',
        endTag: '###',
        size: '832x1216',
        width: '832',
        height: '1216',
        negativePrompt: '',
        zidongdianji:"true",
        maxAutoClicks: '3',
        dbclike:"false",
        displayMode:"默认",
        steps: '28',

        currentMode: 'sd', // 'sd' or 'nai' or 'comfyui'

        // 提示词预设 (通用)
        yushe:{"默认":{"positivePrompt":'',"negativePrompt":''}},
        yusheid:"默认",
        fixedPrompt: '',
        AQT:'',
        UCP:'',


        // SD 独有
        sdUrl: 'http://127.0.0.1:7860',
        sdCfgScale: '7',
        seed: '-1',
        restoreFaces: 'false',
        samplerName: 'DPM++ 2M Karras',
        sdScheduler: 'automatic',
        sdModel: '',
        adetailerEnabled: 'false',
        adModel: 'face_yolov8n.pt',
        adDenoisingStrength: '0.4',
        adMaskBlur: '4',
        adInpaintPadding: '32',
        enableHr: 'false',
        hrScale: '1.5',
        hrDenoisingStrength: '0.4',
        hrUpscaler: 'R-ESRGAN 4x+ Anime6B',
        hrSecondPassSteps: '15',
        controlNetEnabled: 'false',
        controlNetUnits: '[]', // 存储所有ControlNet单元配置的JSON字符串

        // NAI 独有
        naiApiUrl: 'https://std.loliyc.com/generate',
        naiToken: '111',
        naiChannel: 'proxy', // <--- 新增:'proxy' 或 'official'
        nai3sm: 'true',
        nai3dyn: 'true',
        nai3Variety: 'true',
        nai3Deceisp: 'true',
        AI_use_coords: 'false', // <--- 新增的行// ComfyUI 独有
        naiPositivePrompt: 'best quality, amazing quality, very aesthetic, absurdres',
        naiModel: 'nai-diffusion-3',
        naiSampler: 'k_euler_ancestral',
        naiScale: '5',
        naiCfg: '0',
        naiNoiseSchedule: 'karras',
        naiVibeTransferEnabled: 'false',
        naiVibeTransferImages: '[]',

        // ComfyUI 独有
        comfyuiUrl: 'http://127.0.0.1:8188',
        comfyuiWorkflows: {
            "默认脸部修复 (FaceDetailer)": JSON.stringify(defaultComfyWorkflow, null, 2),
            "基础绘图 (无修复功能)": JSON.stringify(simpleWorkflow, null, 2),
            "绘图(高清修复+脸部细节)": JSON.stringify(hiresWorkflow, null, 2)
        },
        comfyuiCurrentWorkflow: "默认脸部修复 (FaceDetailer)",
        comfyuiModel: "",
        comfyuiSampler: "euler_ancestral",
        comfyuiScheduler: "karras",
        comfyuiLora: "None",
        comfyuiLoraStrength: '1.0',
        comfyuiLora2: "None",
        comfyuiLoraStrength2: '1.0',
        comfyuiLora3: "None",
        comfyuiLoraStrength3: '1.0',
        comfyuiLora4: "None",
        comfyuiLoraStrength4: '1.0',
        faceDetailerSteps: '12',
        faceDetailerGuideSize: '512',
        faceDetailerMaxSize: '1024',
        faceDetailerCfg: '4.5',
        faceDetailerSampler: 'euler_ancestral',
        faceDetailerScheduler: 'karras',
        faceDetailerModel: 'bbox/face_yolov8s.pt', //脸部修复模型
        comfyuiUseCustomWorkflowOnly: 'false',


        // ComfyUI Ultimate SD Upscale 独有
        comfyuiUpscaleModel: 'RealESRGAN_x4plus_anime_6B.pth',
        comfyuiUltimateUpscaleBy: '1.3',
        comfyuiUltimateSteps: '15',
        comfyuiUltimateCfg: '5',
        comfyuiUltimateSampler: 'euler_ancestral',
        comfyuiUltimateScheduler: 'karras',
        comfyuiUltimateDenoise: '0.2',
        comfyuiUltimateModeType: 'Linear',
    };
    let settings = {};

    let tempYushe = GM_getValue("yushe", defaultSettings.yushe);
    if(typeof tempYushe === 'string') tempYushe = JSON.parse(tempYushe);
    if(tempYushe?.["默认"]?.hasOwnProperty("fixedPrompt")){
        for (const key in tempYushe) {
            if (tempYushe[key].hasOwnProperty('fixedPrompt')) {
                tempYushe[key].positivePrompt = tempYushe[key].fixedPrompt;
                delete tempYushe[key].fixedPrompt;
            }
        }
        GM_setValue("yushe", JSON.stringify(tempYushe));
    }


    for (const [key, defaultValue] of Object.entries(defaultSettings)) {
        let value = GM_getValue(key, defaultValue);
        if (value === defaultValue) {
            if(typeof defaultValue === 'object'){
                 GM_setValue(key, JSON.stringify(defaultValue));
            } else {
                 GM_setValue(key, defaultValue);
            }
        }
        if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
            try {
              settings[key] = JSON.parse(value);
            } catch (error) {
              console.error(`Failed to parse setting ${key}, using default.`);
              settings[key] = defaultValue;
            }
        } else {
            settings[key] = value;
        }
    }


    function addNewElement() {
        const targetElement = document.querySelector('#option_toggle_AN');
        if (targetElement) {
            clearInterval(ster);
            const newElement = document.createElement('a');
            newElement.id = 'option_toggle_AN2';
            const icon = document.createElement('i');
            icon.className = 'fa-lg fa-solid fa-note-sticky';
            newElement.appendChild(icon);
            const span = document.createElement('span');
            span.setAttribute('data-i18n', "打开设置");
            span.textContent = '打开文生图设置';
            newElement.appendChild(span);
            targetElement.parentNode.insertBefore(newElement, targetElement.nextSibling);
            console.log("New element added successfully");
            document.getElementById('option_toggle_AN2').addEventListener('click', showSettingsPanel);
       }
    }
    // 【请删除旧的 openTagsSupermarketModal 函数,然后将下面的完整函数粘贴到原处】

    function openTagsSupermarketModal() {
        const TAGS_SUPERMARKET_PROMPT_KEY = 'tagsSupermarketPrompt';
        // --- (开始) 验证数据源插件是否存在 ---
        if (typeof unsafeWindow.ChatomiPlugins === 'undefined' || typeof unsafeWindow.ChatomiPlugins.tagSupermarketData === 'undefined') {
            alert('错误:未找到“标签超市数据源”插件或数据加载失败。\n请确保您已安装并启用了数据源插件。');
            return;
        }
        const tagSupermarketData = unsafeWindow.ChatomiPlugins.tagSupermarketData;
        // --- (结束) 验证数据源插件是否存在 ---

        // (核心新增) 为标签超市的预览区定义一个专属的、固定的位置哈希
        const TAGS_SUPERMARKET_PREVIEW_HASH = 'tags_supermarket_preview_slot_v1';

        const existingModal = document.querySelector('.tags-supermarket-modal-overlay');
        if (existingModal) {
            existingModal.remove();
        }

        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'tags-supermarket-modal-overlay';
        // HTML 和 CSS 结构与上一个版本完全相同,无需修改
        modalOverlay.innerHTML = `
        <style>
        /* --- 标签超市专属样式 --- */
        .tags-supermarket-modal-overlay {
            position: fixed; top: 0; left: 0;
            width: 100%; height: 100%;
            background-color: rgba(0, 0, 0, 0.7);
            z-index: 10002;
            display: flex; justify-content: center; align-items: center;
            opacity: 0;
            transition: opacity 0.2s ease;
        }
        .tags-supermarket-modal-overlay.visible {
            opacity: 1;
        }

        .tags-supermarket-modal {
            width: min(7680px, 90vw);
            height: min(4320px, 85vh);
            background-color: var(--bg-main, #1e1e2e);
            color: var(--text-primary, #c0caf5);
            border-radius: 12px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            border: 1px solid var(--border-color, #414868);
            display: flex;
            flex-direction: column;
            transform: scale(0.95);
            opacity: 0;
            transition: transform 0.2s ease, opacity 0.2s ease;
        }
        .tags-supermarket-modal-overlay.visible .tags-supermarket-modal {
            transform: scale(1);
            opacity: 1;
        }

        .ts-header {
            padding: 12px 20px;
            border-bottom: 1px solid var(--border-color, #414868);
            display: flex; justify-content: space-between; align-items: center;
            background-color: var(--bg-sidebar, #1a1b26);
        }
        .ts-header h2 { margin: 0; font-size: 1.2em; }

        .tag-supermarket-content {
            padding: 15px;
            overflow: hidden;
            flex-grow: 1;
            display: flex;
            gap: 15px;
        }

        .ts-main-panel {
            flex: 1;
            min-width: 0;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .ts-generation-area {
            flex: 0 0 320px;
            display: flex;
            flex-direction: column;
            gap: 15px;
            border: 1px solid var(--border-color, #414868);
            border-radius: 8px;
            padding: 15px;
            background-color: rgba(0,0,0,0.15);
        }
        #ts-image-preview {
            flex-grow: 1;
            background-color: var(--bg-input, #2a2e3e);
            border: 1px solid var(--border-color, #414868);
            border-radius: 8px;
            display: flex;
            justify-content: center;
            align-items: center;
            color: var(--text-secondary);
            font-size: 0.9em;
            overflow: hidden;
            position: relative;
        }
        #ts-image-preview .loading-spinner {
            font-size: 2em;
            color: var(--accent-primary);
            animation: spin 1.5s linear infinite;
        }
        @keyframes spin { 100% { transform: rotate(360deg); } }

        #ts-image-preview img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        #ts-image-preview img:hover {
            transform: scale(1.03);
        }

        #ts-generate-image-btn {
            padding: 12px 20px;
            font-size: 1.1em;
            font-weight: bold;
            background-color: #ffc107;
            color: #1a1b26;
            border-color: #f5b800;
            transition: all 0.2s ease;
        }
        #ts-generate-image-btn:hover:not(:disabled) {
            background-color: #ffd54f;
            box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
        }
        #ts-generate-image-btn:disabled {
            background-color: #6c757d;
            border-color: #5a6268;
            color: #c0caf5;
            cursor: not-allowed;
            opacity: 0.7;
        }


        .ts-section {
            padding: 12px;
            border: 1px solid var(--border-color, #414868);
            border-radius: 8px;
            background-color: rgba(0,0,0,0.15);
        }
        #ts-prompt-area {
            width: 100%;
            height: 80px;
            resize: vertical;
            background-color: var(--bg-input, #2a2e3e);
            color: var(--text-primary, #c0caf5);
            border: 1px solid var(--border-color, #414868);
            border-radius: 5px;
            padding: 8px 10px;
            box-sizing: border-box;
        }
        .ts-search-area { display: flex; gap: 10px; align-items: center; }
        #ts-search-input {
            flex-grow: 1;
            margin-bottom: 0 !important;
            background-color: var(--bg-input, #2a2e3e);
            color: var(--text-primary, #c0caf5);
            border: 1px solid var(--border-color, #414868);
            border-radius: 5px;
            padding: 8px 10px;
        }

        .ts-categories-container {
            display: flex; flex-direction: column; gap: 10px;
        }
        .ts-main-categories, .ts-sub-categories {
            display: flex; flex-wrap: wrap; gap: 8px;
        }
        .ts-categories-container .panel-button.active {
            background-color: var(--accent-primary, #7aa2f7);
            color: var(--bg-main, #1e1e2e);
            font-weight: bold;
            border-color: var(--accent-primary, #7aa2f7);
        }

        .ts-tags-display-area {
            flex-grow: 1;
            overflow-y: auto;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
            gap: 10px;
            padding: 10px;
            align-content: start;
        }
        .tag-card {
            background-color: var(--bg-input, #2a2e3e);
            border: 1px solid var(--border-color, #414868);
            border-radius: 6px;
            padding: 8px 12px;
            cursor: pointer;
            transition: all 0.2s ease;
            height: 55px;
            box-sizing: border-box;
            text-align: center;
            display: flex;
            flex-direction: column;
            justify-content: center;
            user-select: none;
            gap: 4px;
            overflow: hidden;
        }
        .tag-card:hover {
            border-color: var(--accent-primary, #7aa2f7);
            transform: translateY(-2px);
            box-shadow: 0 4px 10px rgba(0,0,0,0.3);
        }
        .tag-card .tag-cn {
            font-size: 0.9em;
            color: var(--text-primary);
        }
        .tag-card .tag-en {
            font-size: 0.8em;
            color: var(--text-secondary);
        }
        .tag-card .tag-cn,
        .tag-card .tag-en {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            width: 100%;
        }
        .tag-card.selected {
            background-color: var(--accent-primary, #7aa2f7);
            border-color: var(--accent-primary, #7aa2f7);
            transform: translateY(0);
        }
        .tag-card.selected:hover {
            box-shadow: none;
        }
        .tag-card.selected .tag-cn,
        .tag-card.selected .tag-en {
            color: var(--bg-main, #1e1e2e);
            font-weight: bold;
        }

        .ts-footer {
            padding: 12px 20px;
            border-top: 1px solid var(--border-color, #414868);
            display: flex; justify-content: space-between; align-items: center;
            background-color: var(--bg-sidebar, #1a1b26);
        }
        .ts-footer span { font-size: 0.85em; color: var(--text-secondary, #a9b1d6); }

        @media screen and (max-width: 800px) {
            .tags-supermarket-modal {
                position: fixed !important;
                top: 2.5vh !important;
                left: 5vw !important;
                right: 5vw !important;
                bottom: 2.5vh !important;
                width: 90vw !important;
                height: 85vh !important;
                transform: none !important;
                transform-origin: top center !important;
                overflow-y: auto; /* 让整个模态框可以滚动 */
            }

            .tag-supermarket-content {
                flex-direction: column;
                overflow: visible; /* 允许内容溢出,触发父级滚动条 */
                height: auto;      /* 高度自适应 */
                flex-grow: 0;      /* 不再填充空间 */
            }

            .ts-tags-display-area {
                overflow: visible;     /* 取消内部滚动 */
                height: auto;          /* 高度自适应 */
                flex-grow: 0;          /* 不再尝试填充空间 */
                min-height: 150px;     /* 避免内容过少时太扁 */
            }
            /* 1. 一行 4 个:把 120px 改小 */
            .ts-tags-display-area {
                grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
            }

            /* 2. 卡片整体缩小 */
            .tag-card {
                height: 45px;          /* 原来是 55px */
                padding: 6px 8px;      /* 可以略微再收一点内边距 */
            }
            .tag-card .tag-cn {
                font-size: 0.8em;      /* 原来是 0.9em */
            }
            .tag-card .tag-en {
                font-size: 0.75em;     /* 原来是 0.8em */
            }
        }
        </style>

        <div class="tags-supermarket-modal">
            <div class="ts-header">
                <h2><i class="fa-solid fa-tags" style="margin-right: 10px; color: var(--accent-green, #9ece6a);"></i>标签超市</h2>
                <button id="close-ts-modal-btn" class="panel-button danger" style="padding: 6px 10px; font-size: 0.9em;"><i class="fa-solid fa-times"></i> 关闭</button>
            </div>

            <div class="tag-supermarket-content">
                <div class="ts-main-panel">
                    <div class="ts-section">
                        <textarea id="ts-prompt-area" placeholder="点击下方的标签会自动添加到这里,将作为生图的正向提示词..."></textarea>
                    </div>

                    <div class="ts-section ts-search-area">
                        <input type="text" id="ts-search-input" placeholder="输入中文或英文关键词搜索...">
                        <button id="ts-fuzzy-search-btn" class="panel-button"><i class="fa-solid fa-search"></i> 模糊搜索</button>
                        <button id="ts-exact-search-btn" class="panel-button"><i class="fa-solid fa-binoculars"></i> 精确搜索</button>
                    </div>

                    <div class="ts-section ts-categories-container">
                        <div class="ts-main-categories"></div>
                        <div class="ts-sub-categories"></div>
                    </div>

                    <div class="ts-section ts-tags-display-area">
                        <p style="color: var(--text-secondary, #a9b1d6);">请先从上方选择分类</p>
                    </div>
                </div>

                <div class="ts-generation-area">
                    <div id="ts-image-preview">
                        <span>图片将在此处生成</span>
                    </div>
                    <button id="ts-generate-image-btn" class="panel-button">
                        <i class="fa-solid fa-wand-magic-sparkles" style="margin-right: 8px;"></i>
                        <span>生成图片</span>
                    </button>
                </div>
            </div>

            <div class="ts-footer">
                <span>提示: 点击标签添加/移除。双击生成的图片可放大。</span>
                <button id="ts-copy-prompt-btn" class="panel-button primary"><i class="fa-solid fa-copy"></i> 复制提示词并关闭</button>
            </div>
        </div>
    `;
        document.body.appendChild(modalOverlay);

        const modal = modalOverlay.querySelector('.tags-supermarket-modal');
        const promptTextarea = document.getElementById('ts-prompt-area');
        const mainCatContainer = modal.querySelector('.ts-main-categories');
        const subCatContainer = modal.querySelector('.ts-sub-categories');
        const tagsContainer = modal.querySelector('.ts-tags-display-area');
        const generateBtn = document.getElementById('ts-generate-image-btn');
        const imagePreviewArea = document.getElementById('ts-image-preview');
        promptTextarea.value = GM_getValue(TAGS_SUPERMARKET_PROMPT_KEY, '');

        let currentMainCategory = null;
        let currentSubCategory = null;
        let isGeneratingInModal = false;
        const pinyinSorter = (a, b) => {
            const collator = new Intl.Collator('zh-Hans-CN', { usage: 'sort' });
            const pinyinResult = collator.compare(a, b);
            if (pinyinResult !== 0) return pinyinResult;
            return a.length - b.length;
        };

        function escapeRegExp(string) {
            return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        }


        /**
     * @param {string} fullPrompt - 完整的提示词字符串。
     * @param {string} tag - 要检查的单个标签字符串。
     * @returns {boolean} - 如果标签存在,则返回true。
     */
        function isTagSelected(fullPrompt, tag) {
            const escapedTag = escapeRegExp(tag);
            const tagRegex = new RegExp(`(^|\\s*,\\s*)${escapedTag}(\\s*,\\s*|$)`, 'g');
            return tagRegex.test(fullPrompt);
        }

        function updateAllTagSelections() {
            const currentPrompt = promptTextarea.value;
            const allTagCards = tagsContainer.querySelectorAll('.tag-card');

            allTagCards.forEach(card => {
                const tagValue = card.dataset.tagValue;
                if (!tagValue) return;

                if (isTagSelected(currentPrompt, tagValue)) {
                    card.classList.add('selected');
                } else {
                    card.classList.remove('selected');
                }
            });
        }

        const displayImageInPreview = (imgSrc, imgId) => {
            imagePreviewArea.innerHTML = '';
            const img = document.createElement('img');
            img.src = imgSrc;
            img.alt = '标签超市生成的图片';
            img.dataset.imageId = imgId;
            img.addEventListener('dblclick', () => { showImageLightbox(imgSrc, imgId); });
            imagePreviewArea.appendChild(img);
        };

        function renderMainCategories() {
            mainCatContainer.innerHTML = '';
            const sortedCategories = Object.keys(tagSupermarketData).sort(pinyinSorter);
            for (const catName of sortedCategories) {
                const btn = document.createElement('button');
                btn.className = 'panel-button';
                btn.textContent = catName;
                btn.dataset.category = catName;
                btn.addEventListener('click', () => {
                    currentMainCategory = catName;
                    currentSubCategory = null;
                    highlightActive(mainCatContainer, btn);
                    subCatContainer.innerHTML = '';
                    tagsContainer.innerHTML = `<p style="color: var(--text-secondary, #a9b1d6);">请选择子分类</p>`;
                    renderSubCategories();
                });
                mainCatContainer.appendChild(btn);
            }
        }

        function renderSubCategories() {
            subCatContainer.innerHTML = '';
            if (!currentMainCategory) return;
            const sortedSubCategories = Object.keys(tagSupermarketData[currentMainCategory]).sort(pinyinSorter);
            for (const subCatName of sortedSubCategories) {
                const btn = document.createElement('button');
                btn.className = 'panel-button';
                btn.textContent = subCatName;
                btn.dataset.category = subCatName;
                btn.addEventListener('click', () => {
                    currentSubCategory = subCatName;
                    highlightActive(subCatContainer, btn);
                    renderTags();
                });
                subCatContainer.appendChild(btn);
            }
        }

        function renderTags(tagsToShow = null) {
            tagsContainer.innerHTML = '';
            let tagsObject = tagsToShow;
            if (!tagsObject) {
                if (currentMainCategory && currentSubCategory) {
                    tagsObject = tagSupermarketData[currentMainCategory][currentSubCategory];
                }
            }

            if (!tagsObject || Object.keys(tagsObject).length === 0) {
                tagsContainer.innerHTML = `<p style="color: var(--text-secondary, #a9b1d6);">${tagsToShow ? '没有找到匹配的标签。' : '没有标签,或请继续选择子分类。'}</p>`;
                return;
            }

            const sortedTagEntries = Object.entries(tagsObject).sort(([cnA], [cnB]) => pinyinSorter(cnA, cnB));
            sortedTagEntries.forEach(([cn, originalEn]) => {
                // let en = originalEn.replace(/(?<!\\)([()])/g, '\\$1'); // 反斜杆添加
                const card = document.createElement('div');
                card.className = 'tag-card';
                const cnSpan = document.createElement('span');
                cnSpan.className = 'tag-cn';
                cnSpan.textContent = cn;

                const enSpan = document.createElement('span');
                enSpan.className = 'tag-en';
                enSpan.textContent = originalEn

                card.appendChild(cnSpan);
                card.appendChild(enSpan);
                const tagValue = originalEn;
                card.dataset.tagValue = tagValue;

                card.addEventListener('click', () => {
                    const currentPrompt = promptTextarea.value;

                    if (isTagSelected(currentPrompt, tagValue)) {
                        const escapedTag = escapeRegExp(tagValue);
                        const regexToRemove = new RegExp(`(^|\\s*,\\s*)${escapedTag}(?=(\\s*,\\s*|$))`, 'g');

                        let newPrompt = currentPrompt.replace(regexToRemove, '');

                        newPrompt = newPrompt.trim().replace(/^,/, '').trim();
                        newPrompt = newPrompt.replace(/,$/, '').trim();
                        newPrompt = newPrompt.replace(/,\s*,/g, ',');

                        promptTextarea.value = newPrompt;

                    } else {
                        if (currentPrompt.length > 0 && !/,\s*$/.test(currentPrompt)) {
                            promptTextarea.value += ', ';
                        }
                        promptTextarea.value += tagValue;
                    }

                    promptTextarea.dispatchEvent(new Event('input', { bubbles: true }));
                });
                tagsContainer.appendChild(card);
            });
            updateAllTagSelections();
        }

        async function handleModalImageGeneration() {
            if (isGeneratingInModal) return;
            const promptFromTags = promptTextarea.value.trim();
            if (!promptFromTags) {
                alert('提示词区域为空,请输入或选择一些标签后再生成。');
                return;
            }
            isGeneratingInModal = true;
            generateBtn.disabled = true;
            imagePreviewArea.innerHTML = `<i class="fa-solid fa-spinner loading-spinner"></i>`;
            generateBtn.querySelector('span').textContent = '生成中...';
            try {
                const generatorFunction = {sd: sd, nai: naiGenerate, comfyui: comfyuiGenerate}[settings.currentMode];
                if (!generatorFunction) throw new Error(`未知的生成模式: ${settings.currentMode}`);
                const fakeButton = {
                    id: "image_modal_generator",
                    dataset: { link: promptFromTags, locationHash: TAGS_SUPERMARKET_PREVIEW_HASH },
                    isModalCall: true
                };
                let dataURL = null;
                if (settings.currentMode === 'sd') {
                    dataURL = await generatorFunction(fakeButton, settings.width, settings.height);
                } else {
                    dataURL = await generatorFunction(fakeButton);
                }
                if (!dataURL) throw new Error("生成图片失败或未获取到图片数据。请检查控制台。");
                const finalImageId = locationToImageIdMap[TAGS_SUPERMARKET_PREVIEW_HASH];
                displayImageInPreview(dataURL, finalImageId);
            } catch (error) {
                console.error('[标签超市] 图片生成失败:', error);
                imagePreviewArea.innerHTML = `<span style="text-align:center; padding:10px;">生成失败<br><small>${error.message}</small></span>`;
                alert(`图片生成失败: ${error.message}`);
            } finally {
                isGeneratingInModal = false;
                generateBtn.disabled = false;
                generateBtn.querySelector('span').textContent = '生成图片';
            }
        }

        function highlightActive(container, activeButton) {
            container.querySelectorAll('.panel-button').forEach(btn => btn.classList.remove('active'));
            if(activeButton) activeButton.classList.add('active');
        }

        function handleSearch(isFuzzy) {
            const searchTerm = document.getElementById('ts-search-input').value.toLowerCase().trim();
            if (!searchTerm) return;
            highlightActive(mainCatContainer, null);
            highlightActive(subCatContainer, null);
            currentMainCategory = null;
            currentSubCategory = null;
            const results = {};
            for (const mainCat in tagSupermarketData) {
                for (const subCat in tagSupermarketData[mainCat]) {
                    for (const [cn, en] of Object.entries(tagSupermarketData[mainCat][subCat])) {
                        const enLower = en.toLowerCase();
                        const cnLower = cn.toLowerCase();
                        let match = false;
                        if (isFuzzy) {
                            match = enLower.includes(searchTerm) || cnLower.includes(searchTerm);
                        } else {
                            match = enLower === searchTerm || cnLower === searchTerm;
                        }
                        if (match) {
                            results[cn] = en;
                        }
                    }
                }
            }
            renderTags(results);
        }

        let escListener = null;
        function closeModal() {
            GM_setValue(TAGS_SUPERMARKET_PROMPT_KEY, promptTextarea.value);
            if (escListener) document.removeEventListener('keydown', escListener);
            modalOverlay.classList.remove('visible');
            setTimeout(() => modalOverlay.remove(), 200);
        }
        escListener = (e) => { if (e.key === "Escape") closeModal(); };

        document.addEventListener('keydown', escListener);
        promptTextarea.addEventListener('input', updateAllTagSelections);
        modalOverlay.querySelector('#close-ts-modal-btn').addEventListener('click', closeModal);
        modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) closeModal(); });
        modalOverlay.querySelector('#ts-fuzzy-search-btn').addEventListener('click', () => handleSearch(true));
        modalOverlay.querySelector('#ts-exact-search-btn').addEventListener('click', () => handleSearch(false));
        document.getElementById('ts-search-input').addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); handleSearch(true); } });
        modalOverlay.querySelector('#ts-copy-prompt-btn').addEventListener('click', () => {
            GM_setValue(TAGS_SUPERMARKET_PROMPT_KEY, promptTextarea.value);
            const textToCopy = promptTextarea.value.trim().replace(/,\s*$/, '').trim(); // 更好的清理结尾逗号
            navigator.clipboard.writeText(textToCopy).then(() => {
                showToast('提示词已复制!');
                closeModal();
            }).catch(err => {
                console.error('复制失败:', err);
                showToast('复制失败!', 'error');
            });
        });
        generateBtn.addEventListener('click', handleModalImageGeneration);

        renderMainCategories();
        const lastImageId = locationToImageIdMap[TAGS_SUPERMARKET_PREVIEW_HASH];
        if (lastImageId) {
            getItemImg(lastImageId).then(cachedImgData => { if (cachedImgData) displayImageInPreview(cachedImgData, lastImageId); });
        }
        updateAllTagSelections();
        setTimeout(() => modalOverlay.classList.add('visible'), 20);
    }










    function createSettingsPanel() {

        const decodeUnicodeBase64 = (b64) => {
            try {
                const binaryString = window.atob(b64);
                const bytes = new Uint8Array(binaryString.length);
                for (let i = 0; i < binaryString.length; i++) {
                    bytes[i] = binaryString.charCodeAt(i);
                }
                return new TextDecoder('utf-8').decode(bytes);
            } catch (e) {
                console.error("", e);
                return "";
            }
        };
        const panel = document.createElement('div');
        panel.id = 'settings-panel';
        panel.className = 'settings-panel-reborn';
        const encodedAuthor = 'QlnojLbomLw=';
        const encodedQqLink = 'aHR0cHM6Ly9xbS5xcS5jb20vcS9YaVdHcURJclFJ';
        const encodedQqText = 'UVHnvqQ=';
        let styles = `
            /* --- 全局与色彩设定 --- */
            .settings-panel-reborn {
                --bg-main: #1e1e2e;
                --bg-sidebar: #1a1b26;
                --bg-content: #1e1e2e;
                --bg-input: #2a2e3e;
                --border-color: #414868;
                --text-primary: #c0caf5;
                --text-secondary: #a9b1d6;
                --accent-primary: #7aa2f7;
                --accent-secondary: #f7768e;
                --accent-green: #9ece6a;
                --shadow-color: rgba(0, 0, 0, 0.3);

                position: fixed; top: 50%; left: 50%;
                transform: translate(-50%, -50%);
                width: min(900px, 95vw);
                height: min(700px, 90vh);
                background-color: var(--bg-main);
                color: var(--text-primary);
                border-radius: 12px;
                box-shadow: 0 10px 30px var(--shadow-color);
                z-index: 10000;
                display: none;
                flex-direction: column;
                overflow: hidden;
            }
            .settings-panel-reborn.visible { display: flex; }

            /* --- 头部 --- */
            .panel-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 12px 20px;
                border-bottom: 1px solid var(--border-color);
                background-color: var(--bg-sidebar);
            }
            .panel-header h2 { margin: 0; font-size: 1.2em; color: var(--text-primary); }
            .panel-header .controls { display: flex; align-items: center; gap: 20px; }
            .panel-header label { display: flex; align-items: center; gap: 8px; font-size: 0.9em;}

            /* --- 主体布局 (侧边栏 + 内容区) --- */
            .panel-body { display: flex; flex-grow: 1; min-height: 0; }
            .panel-sidebar {
                width: 180px;
                flex-shrink: 0;
                background-color: var(--bg-sidebar);
                padding: 15px 0;
                border-right: 1px solid var(--border-color);
                overflow-y: auto;
            }
            .nav-link {
                display: block;
                padding: 10px 20px;
                color: var(--text-secondary);
                text-decoration: none;
                font-weight: 500;
                border-left: 3px solid transparent;
                transition: all 0.2s ease;
            }
            .nav-link:hover { background-color: rgba(122, 162, 247, 0.1); }
            .nav-link.active {
                color: var(--text-primary);
                font-weight: 700;
                background-color: rgba(122, 162, 247, 0.15);
                border-left-color: var(--accent-primary);
            }

            /* --- 内容区 --- */
            .panel-content { flex-grow: 1; padding: 25px; overflow-y: auto; }
            .settings-pane { display: none; }
            .settings-pane.active { display: block; animation: fadeIn 0.3s ease; }
            @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

            .settings-group {
                margin-bottom: 30px;
                padding: 20px;
                border: 1px solid var(--border-color);
                border-radius: 8px;
                background-color: rgba(0,0,0,0.1);
            }
            .settings-group-title {
                font-size: 1.1em;
                font-weight: 600;
                margin-bottom: 15px;
                padding-bottom: 8px;
                border-bottom: 1px solid var(--border-color);
                color: var(--accent-green);
            }

            /* --- 表单元素 --- */
            .settings-panel-reborn label { font-size: 0.95em; color: var(--text-secondary); margin-bottom: 5px; display: block; }
            .settings-panel-reborn input[type="text"],
            .settings-panel-reborn input[type="number"],
            .settings-panel-reborn select,
            .settings-panel-reborn textarea {
                width: 100%;
                background-color: var(--bg-input);
                color: var(--text-primary);
                border: 1px solid var(--border-color);
                border-radius: 5px;
                padding: 8px 10px;
                margin-bottom: 15px;
                box-sizing: border-box;
                transition: border-color 0.2s, box-shadow 0.2s;
            }
            .settings-panel-reborn input:focus, .settings-panel-reborn select:focus, .settings-panel-reborn textarea:focus {
                outline: none;
                border-color: var(--accent-primary);
                box-shadow: 0 0 0 2px rgba(122, 162, 247, 0.3);
            }
            .settings-panel-reborn textarea { height: 70px; resize: vertical; }
            #comfyuiWorkflowEditor {
                height: 300px;
                font-family: monospace;
                font-size: 0.85em;
                white-space: pre;
            }
            .settings-panel-reborn select { appearance: none; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23c0caf5%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-12.8z%22/%3E%3C/svg%3E'); background-repeat: no-repeat; background-position: right 10px top 50%; background-size: 10px auto; padding-right: 30px; }


            /* --- 按钮 --- */
            .panel-button {
                padding: 8px 15px;
                font-size: 0.9em;
                font-weight: 600;
                color: var(--text-primary);
                background-color: #292e42;
                border: 1px solid var(--border-color);
                border-radius: 5px;
                cursor: pointer;
                transition: all 0.2s ease;
            }
            .panel-button:hover { background-color: #3b4261; border-color: var(--accent-primary); }
            .panel-button.primary { background-color: var(--accent-primary); color: var(--bg-main); border-color: var(--accent-primary); }
            .panel-button.primary:hover { opacity: 0.9; }
            .panel-button.danger { background-color: var(--accent-secondary); color: var(--bg-main); border-color: var(--accent-secondary); }
            .panel-button.danger:hover { opacity: 0.9; }

             /* --- 页脚 --- */
            .panel-footer {
                padding: 12px 20px;
                border-top: 1px solid var(--border-color);
                display: flex;
                justify-content: space-between;
                align-items: center;
                background-color: var(--bg-sidebar);
            }
            .panel-footer .footer-links a { color: var(--text-secondary); text-decoration: none; margin: 0 8px; font-size: 0.85em; }
            .panel-footer .footer-links a:hover { color: var(--accent-primary); }
            .panel-footer .footer-buttons button { margin-left: 10px; }


            /* --- 特殊组件 --- */
            .flex-row { display: flex; gap: 15px; align-items: flex-start; }
            .flex-row > div { flex: 1; min-width: 0; }
            .lora-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px 20px; }
            .lora-item { display: contents; } /* 让 item 的子元素直接参与 grid 布局 */
            .lora-item > label { grid-column: 1 / 2; }
            .lora-item > .lora-strength-control { grid-column: 2 / 3; }
            .lora-strength-control { display:flex; align-items:center; gap:10px; margin-top: -5px; }
            .lora-strength-control input[type=range] { width:100%; padding:0; margin:0; accent-color: var(--accent-primary); }
            .lora-strength-value { min-width: 35px; font-weight:bold; color: var(--text-primary); }

            .button-group { display: flex; gap: 10px; align-items: center; margin-bottom: 20px; }
            .button-group > * { margin-bottom: 0 !important; }
            .button-group > label { flex-grow: 1; }

            .switch { position: relative; display: inline-block; width: 44px; height: 24px; vertical-align: middle; }
            .switch input { opacity: 0; width: 0; height: 0; }
            .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #3b4261; transition: .3s; border-radius: 24px; }
            .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }
            input:checked + .slider { background-color: var(--accent-green); }
            input:checked + .slider:before { transform: translateX(20px); }

            /* NAI Vibe Transfer 样式 */
            #naiVibeImageListContainer { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin-top: 10px; }
            #naiVibeImageListContainer .vibe-item { border: 1px solid var(--border-color); border-radius: 8px; padding: 10px; background-color: var(--bg-input); font-size: 0.9em; }
            #naiVibeImageListContainer .vibe-item img { max-width: 100%; border-radius: 4px; margin-bottom: 10px; }
            #naiVibeImageListContainer .vibe-item .controls .slider-label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 4px; color: var(--text-secondary); }
            #naiVibeImageListContainer .vibe-item .controls input[type="range"] { width: 100%; margin: 0; accent-color: var(--accent-primary); height: 4px; }
            #naiVibeImageListContainer .vibe-item .vibe-delete-btn { float: right; padding: 3px 8px; font-size: 12px; background-color: var(--accent-secondary); color: var(--bg-main); margin-bottom: 5px; border-radius: 3px; border: none; cursor: pointer; }
            #naiVibeStatus.error { color: var(--accent-secondary); } #naiVibeStatus.info { color: var(--accent-primary); }

            .cn-image-area {
                flex: 0 0 160px;
            }

            .cn-settings-area {
                flex-grow: 1;
            }

            .cn-image-upload-wrapper {
                width: 160px;
                height: 240px; /* 保持常见竖图比例 */
                border: 2px dashed var(--border-color);
                border-radius: 6px;
                display: flex;
                justify-content: center;
                align-items: center;
                cursor: pointer;
                background-color: var(--bg-input);
                overflow: hidden;
                position: relative;
            }
            .cn-image-upload-wrapper:hover {
                border-color: var(--accent-primary);
            }
            .cn-upload-placeholder {
                text-align: center;
                color: var(--text-secondary);
            }
            .cn-upload-placeholder i {
                font-size: 3em;
                display: block;
                margin-bottom: 10px;
            }
            .cn-preview-img {
                width: 100%;
                height: 100%;
                object-fit: cover;
            }

            /* Danbooru 标签搜索样式*/
            .tag-results-container {
                min-height: 120px;
                max-height: 120px;
                overflow-y: auto;
                border: 1px solid var(--border-color);
                border-radius: 5px;
                padding: 5px;
                background-color: var(--bg-input);
            }
            .tag-result-item {
                padding: 6px 10px;
                margin: 2px 0;
                border-radius: 4px;
                cursor: pointer;
                transition: background-color 0.2s;
                font-size: 0.9em;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .tag-result-item:hover {
                background-color: #3b4261;
            }
            .tag-result-item.selected {
                background-color: var(--accent-primary);
                color: var(--bg-main);
                font-weight: bold;
            }
            .tag-result-item .tag-post-count {
                color: var(--text-secondary);
                font-size: 0.9em;
            }
            .tag-result-item.selected .tag-post-count {
                color: var(--bg-sidebar);
            }
            /* --- 图片缓存查看器样式 --- */
            #cache-info-text { font-size: 0.9em; color: var(--text-secondary); }
            .cache-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
                gap: 15px;
                margin-top: 15px;
                min-height: 150px;
            }
            .cache-item {
                position: relative;
                border: 1px solid var(--border-color);
                border-radius: 6px;
                overflow: hidden;
                aspect-ratio: 1 / 1.5; /* 保持图片比例 */
                cursor: pointer;
                background-color: var(--bg-input);
            }
            .cache-item:hover .delete-cache-btn { opacity: 1; }
            .cache-item img {
                width: 100%; height: 100%;
                object-fit: cover;
                transition: transform 0.2s ease;
            }
            .cache-item:hover img { transform: scale(1.05); }
            .delete-cache-btn {
                position: absolute;
                top: 4px; right: 4px;
                background-color: rgba(247, 118, 142, 0.8); /* a bit transparent */
                color: white;
                border: none;
                border-radius: 50%;
                width: 24px; height: 24px;
                font-size: 14px;
                line-height: 24px;
                text-align: center;
                cursor: pointer;
                opacity: 0;
                transition: opacity 0.2s ease, background-color 0.2s ease;
                z-index: 2;
            }
            .delete-cache-btn:hover { background-color: var(--accent-secondary); }
            .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
                margin-top: 20px;
            }
            .pagination button { padding: 5px 12px; }
            .pagination .page-info { font-size: 0.9em; color: var(--text-secondary); }

            /* --- 图片查看灯箱 --- */
            .lightbox-overlay {
                position: fixed;
                top: 0; left: 0;
                width: 100%; height: 100%;
                background-color: rgba(0, 0, 0, 0.85);
                z-index: 10002;
                display: flex;
                justify-content: center;
                align-items: center;
                padding: 20px;
                box-sizing: border-box;
            }
            .lightbox-content {
                position: relative;
                max-width: 90vw;
                max-height: 90vh;
            }
            .lightbox-content img {
                display: block;
                max-width: 100%;
                max-height: 100%;
                border-radius: 8px;
                box-shadow: 0 10px 40px rgba(0,0,0,0.5);
            }
            .lightbox-close, .lightbox-download {
                position: absolute;
                top: -35px;
                color: white;
                background-color: rgba(65, 72, 104, 0.8);
                border: 1px solid #414868;
                border-radius: 5px;
                cursor: pointer;
                padding: 6px 12px;
                font-size: 0.9em;
                text-decoration: none;
            }
            .lightbox-close { right: 85px; }
            .lightbox-download { right: 0; }
            .lightbox-close:hover, .lightbox-download:hover { background-color: #3b4261; }


            /* 只在 <= 800 px 的屏幕上生效 */
            @media screen and (max-width: 800px) {
                /* 1. 让面板整体变成 80 % 大小 */
                .settings-panel-reborn {
                    transform: translateX(-50%) scale(0.8);   /* 关键:先位移再缩放 */
                    transform-origin: top center;
                }

                .settings-panel-reborn {
                    width: 98vw;
                    height: 95vh;
                    top: 2.5vh;
                }
                .panel-body { flex-direction: column; }
                .panel-sidebar {
                    width: 100%;
                    border-right: none;
                    border-bottom: 1px solid var(--border-color);
                    display: flex;
                    flex-wrap: wrap;
                    justify-content: center;
                    padding: 5px;
                }
                .nav-link {
                    border-left: none;
                    border-bottom: 3px solid transparent;
                    padding: 8px 12px;
                    font-size: 0.85em;
                }
                .nav-link.active {
                    border-bottom-color: var(--accent-primary);
                    border-left-color: transparent;
                }
                .flex-row, .lora-grid { flex-direction: column; gap: 0; grid-template-columns: 1fr; }
                .panel-footer { flex-direction: column; gap: 10px; }
            }
            .controlnet-modal-overlay {
                position: fixed;
                top: 0; left: 0;
                width: 100%; height: 100%;
                background-color: rgba(0, 0, 0, 0.6);
                z-index: 10001; /* 比主设置面板低一点 */
                display: flex;
                justify-content: center;
                align-items: center;
            }
            .controlnet-modal {
                width: min(850px, 90vw);
                max-height: 85vh;
                background-color: #1e1e2e;
                color: var(--text-primary);
                border-radius: 10px;
                box-shadow: 0 8px 25px rgba(0,0,0,0.4);
                border: 1px solid var(--border-color);
                display: flex;
                flex-direction: column;
                z-index: 10002;
                transform: scale(0.95);
                opacity: 0;
                transition: transform 0.2s ease, opacity 0.2s ease;
            }
            .controlnet-modal.visible {
                transform: scale(1);
                opacity: 1;
            }
            @media screen and (max-width: 800px) {
                /* 让 ControlNet 弹窗也继承主面板的移动端缩放/排列规则 */
                .controlnet-modal.settings-panel-reborn {
                    position: fixed !important;
                    top: 2.5vh !important;
                    left: 1vw !important;
                    right: 1vw !important;
                    bottom: 2.5vh !important;
                    width: 98vw !important;
                    height: 95vh !important;
                    transform: none !important;
                    transform-origin: top center !important;
                }

                .controlnet-modal.settings-panel-reborn .controlnet-modal-content {
                    flex-direction: column !important;
                    gap: 15px;
                }

                .controlnet-modal.settings-panel-reborn .settings-group[data-index] {
                    flex-direction: column !important;
                }

                .controlnet-modal.settings-panel-reborn .unit-body {
                    flex-direction: column !important;
                }
                .controlnet-modal.settings-panel-reborn .cn-image-area,
                .controlnet-modal.settings-panel-reborn .cn-settings-area {
                    flex: none !important;
                    width: 100% !important;
                }

                .controlnet-modal.settings-panel-reborn .settings-group-title {
                    flex-direction: column !important;
                    align-items: flex-start !important;
                }
            }
            .controlnet-modal.settings-panel-reborn {
                /* 覆盖掉从主面板继承来的固定定位和居中变换 */
                position: relative; /* 或者 static,让它恢复正常的文档流 */
                top: auto;
                left: auto;
                transform: none; /* 移除 translate(-50%, -50%) */

                /* 覆盖掉固定的宽高,让弹窗自己的宽高规则生效 */
                width: auto;
                height: auto;

                /* 确保它仍然是一个flex容器,这对于其内部布局至关重要 */
                display: flex;
            }
            /* --- 新增结束 --- */
            .controlnet-modal-header {
                padding: 12px 20px;
                border-bottom: 1px solid var(--border-color);
                display: flex;
                justify-content: space-between;
                align-items: center;
                flex-shrink: 0;
            }
            .controlnet-modal-header h3 {
                margin: 0;
                font-size: 1.1em;
            }
            .controlnet-modal-content {
                padding: 20px;
                overflow-y: auto;
                flex-grow: 1;
                display: flex;
                flex-direction: column;
                gap: 20px;
            }
            .controlnet-modal-footer {
                padding: 12px 20px;
                border-top: 1px solid var(--border-color);
                display: flex;
                justify-content: space-between;
                align-items: center;
                flex-shrink: 0;
            }
            .controlnet-modal-footer .unit-count {
                font-size: 0.9em;
                color: var(--text-secondary);
            }
            /* --- Toast 通知样式 --- */
            .toast-notification {
                position: absolute;
                bottom: 20px;
                left: 50%;
                transform: translate(-50%, 20px); /* 初始位置在下方偏离 */
                padding: 10px 20px;
                border-radius: 6px;
                color: #fff;
                font-size: 0.9em;
                font-weight: 500;
                z-index: 10005;
                opacity: 0;
                transition: opacity 0.4s ease, transform 0.4s ease;
                box-shadow: 0 4px 15px rgba(0,0,0,0.25);
                pointer-events: none; /* 防止遮挡下方的点击 */
            }
            .toast-notification.visible {
                opacity: 1;
                transform: translate(-50%, 0); /* 移动到最终位置 */
            }
            .toast-notification.success {
                background-color: var(--accent-green);
            }
            .toast-notification.error {
                background-color: var(--accent-secondary);
            }
        `;
        // -- HTML 骨架 --
        let header = `
            <style>${styles}</style>
            <div class="panel-header">
                <h2>文生图设置</h2>
                <div class="controls">
                    <label>
                        <span id="script-status-label"></span>
                        <label class="switch"><input type="checkbox" id="scriptToggle" ${settings.scriptEnabled ? 'checked' : ''}><span class="slider"></span></label>
                    </label>
                    <label>模式:
                        <select id="currentMode" style="width: auto; padding: 5px; margin-bottom:0;">
                            <option value=sd ${settings.currentMode === 'sd' ? 'selected' : ''}>Stable Diffusion</option>
                            <option value="nai" ${settings.currentMode === 'nai' ? 'selected' : ''}>NovelAI</option>
                            <option value="comfyui" ${settings.currentMode === 'comfyui' ? 'selected' : ''}>ComfyUI</option>
                        </select>
                    </label>
                </div>
            </div>`;

        let body = `
            <div class="panel-body">
                <nav class="panel-sidebar">
                    <a href="#" class="nav-link active" data-target="pane-general">通用设置</a>
                    <a href="#" class="nav-link" data-target="pane-prompts">提示词</a>
                    ${settings.currentMode === 'sd' ? `<a href="#" class="nav-link" data-target="pane-sd">Stable Diffusion</a>` : ''}
                    ${settings.currentMode === 'nai' ? `<a href="#" class="nav-link" data-target="pane-nai">NovelAI</a>` : ''}
                    ${settings.currentMode === 'comfyui' ? `<a href="#" class="nav-link" data-target="pane-comfyui">ComfyUI</a>` : ''}
                    <a href="#" class="nav-link" data-target="pane-misc">高级设置</a>
                    <a href="#" id="open-tags-supermarket-btn" class="nav-link">标签超市</a>
                </nav>
                <main class="panel-content">
                    ${createGeneralPane()}
                    ${createPromptsPane()}
                    ${settings.currentMode === 'sd' ? createSdPane() : ''}
                    ${settings.currentMode === 'nai' ? createNaiPane() : ''}
                    ${settings.currentMode === 'comfyui' ? createComfyuiPane() : ''}
                    ${createMiscPane()}
                </main>
            </div>`;

        let footer = `
            <div class="panel-footer">
                <div class="footer-links">
                    <a id="visit-website-link" style="font-size: 1.5em; font-weight: bold; color: hotpink;">${decodeUnicodeBase64(encodedAuthor)}</a> |
                    <a href="${decodeUnicodeBase64(encodedQqLink)}" target="_blank" style="font-size: 1.5em; font-weight: bold; color: hotpink;">${decodeUnicodeBase64(encodedQqText)}</a> |
                    <a href="https://spell.novelai.dev/" target="_blank" style="font-size: 1.5em; font-weight: bold; color: hotpink;">AI图片信息提取</a> |
                </div>
                <div class="footer-buttons">
                    <button id="reset-current-mode-settings" class="panel-button">重置当前模式设置</button>
                    <button id="Clear-Cache" class="panel-button danger">清除图片缓存</button>
                    <button id="save-settings" class="panel-button primary">保存并关闭</button>
                    <button id="close-settings" class="panel-button">取消</button>
                </div>
            </div>`;




        panel.innerHTML = header + body + footer;
        document.body.appendChild(panel);

        // -- 绑定事件 --
        document.querySelectorAll('.nav-link').forEach(link => {
            link.addEventListener('click', e => {
                e.preventDefault();
                const targetId = e.currentTarget.dataset.target;

                // 【修复核心】如果点击的链接没有 data-target 属性,
                // 说明它不是一个标准的窗格切换链接(比如“标签超市”按钮),
                // 那么就直接返回,不执行后续的窗格切换逻辑,避免报错。
                if (!targetId) {
                    return;
                }

                // 下面是更健壮的窗格切换逻辑
                const currentActiveLink = panel.querySelector('.nav-link.active');
                if (currentActiveLink) {
                    currentActiveLink.classList.remove('active');
                }
                e.currentTarget.classList.add('active');

                const currentActivePane = panel.querySelector('.settings-pane.active');
                if (currentActivePane) {
                    currentActivePane.classList.remove('active');
                }

                const newPane = panel.querySelector(`#${targetId}`);
                if (newPane) {
                    newPane.classList.add('active');
                }
            });
        });

        document.getElementById('currentMode').addEventListener('change', function() {
          GM_setValue('currentMode', this.value);
          alert('模式已切换,请保存后重新打开设置面板以应用更改。');
        });

        document.getElementById('save-settings').addEventListener('click', saveSettings);
        document.getElementById('close-settings').addEventListener('click', closeSettings);
        document.getElementById('Clear-Cache').addEventListener('click', clearCache);
        document.getElementById('reset-current-mode-settings').addEventListener('click', resetCurrentModeSettings);
        document.getElementById('scriptToggle').addEventListener('change', function() {
            settings.scriptEnabled = this.checked;
            GM_setValue('scriptEnabled', this.checked);
            const statusLabel = document.getElementById('script-status-label'); // 使用ID定位
            if (statusLabel) { // 增加一个安全检查
                statusLabel.textContent = this.checked ? '脚本已启用' : '脚本已禁用';
                statusLabel.style.color = this.checked ? 'var(--accent-green)' : 'var(--accent-secondary)';
            }
        });

        document.getElementById('size').addEventListener('change', size_change);

        document.getElementById('yusheid').addEventListener('change', tishici_change);
        document.getElementById('tishici_save_style').addEventListener('click', tishici_save);
        document.getElementById('tishici_delete_style').addEventListener('click', tishici_delete);
        document.getElementById('open-tags-supermarket-btn').addEventListener('click', (e) => {
            e.preventDefault();
            // 切换活动链接的视觉效果
            const sidebar = document.querySelector('.panel-sidebar');
            sidebar.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active'));
            e.currentTarget.classList.add('active');

            openTagsSupermarketModal();
        });

        // -- 新增: 绑定Danbooru标签搜索事件 --
        document.getElementById('tagSearchBtn').addEventListener('click', searchDanbooruTags);
        document.getElementById('copyTagButton').addEventListener('click', copySelectedTag);
        document.getElementById('fillTagButton').addEventListener('click', fillSelectedTag);
        document.getElementById('tagSearchInput').addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault(); // 防止回车触发表单提交等默认行为
                document.getElementById('tagSearchBtn').click();
            }
        });


        if (settings.currentMode === 'sd') {
          document.getElementById('enableHr').addEventListener('change', function() { settings.enableHr = this.checked ? 'true' : 'false'; GM_setValue('enableHr', settings.enableHr); });
          document.getElementById('refreshSdOptions').addEventListener('click', fetchSdOptions);
          document.getElementById('loraWeight').addEventListener('input', function() { document.getElementById('loraWeightValue').textContent = parseFloat(this.value).toFixed(2); });
          document.getElementById('insertLoraBtn').addEventListener('click', function(e) {
              e.preventDefault();
              const loraSelect = document.getElementById('sdLora');
              const selectedLora = loraSelect.value;
              if (!selectedLora || selectedLora === "") { alert('请先选择一个LoRA!'); return; }
              const weight = document.getElementById('loraWeight').value;
              const positivePromptTextarea = document.getElementById('positivePrompt');
              const loraTag = `<lora:${selectedLora}:${parseFloat(weight).toFixed(2)}>`;
              if (positivePromptTextarea.value.trim() !== '' && !positivePromptTextarea.value.endsWith(',')) { positivePromptTextarea.value += ', '; }
              positivePromptTextarea.value += loraTag;
          });


        } else if (settings.currentMode === 'nai') {
            document.getElementById('naiVibeTransferEnabled').addEventListener('change', function() { settings.naiVibeTransferEnabled = this.checked ? 'true' : 'false'; GM_setValue('naiVibeTransferEnabled', settings.naiVibeTransferEnabled); });
            document.getElementById('naiVibeUploadBtn').addEventListener('click', () => document.getElementById('naiVibeUploadInput').click());
            document.getElementById('naiVibeUploadInput').addEventListener('change', handleVibeImageUpload);
        } else if (settings.currentMode === 'comfyui') {
            document.getElementById('refreshComfyuiOptions').addEventListener('click', fetchComfyuiOptions);
            document.getElementById('comfyuiCurrentWorkflow').addEventListener('change', workflow_change);
            document.getElementById('workflow_update_from_params').addEventListener('click', updateWorkflowFromSettings);
            document.getElementById('workflow_save').addEventListener('click', workflow_save);
            document.getElementById('workflow_delete').addEventListener('click', workflow_delete);
            document.querySelectorAll('.lora-strength-control input[type="range"]').forEach(slider => {
                slider.addEventListener('input', function() {
                    this.nextElementSibling.textContent = parseFloat(this.value).toFixed(2);
                });
            });
        }
        return panel;
    }

    // -- Pane 生成函数 --
    function createGeneralPane() {
        // 对于ComfyUI,cfg使用sdCfgScale的设置,通用化处理
        const cfgValue = settings.currentMode === 'nai' ? settings.naiCfg : settings.sdCfgScale;
        const cfgId = settings.currentMode === 'nai' ? 'naiCfg' : 'sdCfgScale';
        const cfgLabel = settings.currentMode === 'nai' ? '引导 (Scale)' : 'CFG Scale';
        const scaleLabel = settings.currentMode === 'nai' ? 'CFG' : '引导 (Scale)'; // NAI的scale和cfg标签与SD/Comfy相反

        return `
        <div id="pane-general" class="settings-pane active">
            <div class="settings-group">
                <h3 class="settings-group-title">尺寸与分辨率</h3>
                <div class="flex-row">
                    <div><label>预设尺寸: <select id="size">
                        ${settings.currentMode === 'nai' ? `
                        <option value="竖图">竖图 (832x1216)</option>
                        <option value="横图">横图 (1216x832)</option>
                        <option value="方图">方图 (1024x1024)</option>
                        <option value="Custom">自定义(需要消耗点数)</option>
                        ` : `
                        <option value="832x1216">832x1216 (推荐)</option>
                        <option value="1216x832">1216x832</option>
                        <option value="512x768">512x768</option>
                        <option value="768x512">768x512</option>
                        <option value="512x512">512x512</option>
                        <option value="1024x1024">1024x1024</option>
                        <option value="Custom">自定义</option>
                        `}
                    </select></label></div>
                    <div><label>宽度 (Width): <input type="number" id="width" value="${settings.width}"></label></div>
                    <div><label>高度 (Height): <input type="number" id="height" value="${settings.height}"></label></div>
                </div>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">核心参数</h3>
                 <div class="flex-row">
                    ${settings.currentMode === 'nai' ? `
                        <div><label>步数 (Steps): <input type="number" id="steps" value="${settings.steps}"></label></div>
                        <div><label>${scaleLabel}: <input type="number" id="naiScale" step="0.5" value="${settings.naiScale}"></label></div>
                        <div><label>${cfgLabel}: <input type="number" id="${cfgId}" step="0.5" value="${cfgValue}"></label></div>
                    ` : `
                        <div><label>步数 (Steps): <input type="number" id="steps" value="${settings.steps}"></label></div>
                        <div><label>${cfgLabel}: <input type="number" id="${cfgId}" step="0.5" value="${cfgValue}"></label></div>
                        ${settings.currentMode === 'sd' ? `<div><label>种子 (Seed): <input type="number" id="seed" value="${settings.seed}"></label></div>` : `<div><label>种子 (Seed): <input type="text" value="-1 (自动)" disabled></label></div>`}
                   `}
                </div>
            </div>
        </div>`;
    }

    function createPromptsPane() {
        const positivePromptValue = (settings.currentMode === 'sd' || settings.currentMode === 'comfyui') ? settings.fixedPrompt : settings.naiPositivePrompt;
        return `
        <div id="pane-prompts" class="settings-pane">
            <div class="settings-group">
                <h3 class="settings-group-title">标签搜索</h3>
                <label>通过关键词查找标准化的标签(需要挂VPN):</label>
                <div class="button-group" style="margin-bottom: 10px;">
                    <input type="text" id="tagSearchInput" placeholder="例如:原神..." style="margin-bottom: 0;">
                    <button id="tagSearchBtn" class="panel-button" title="搜索标签"><i class="fa-solid fa-search"></i></button>
                </div>
                <div class="flex-row" style="align-items: stretch; gap: 10px;">
                    <div style="flex-grow: 3;">
                         <label>搜索结果 (点击选择):</label>
                         <div id="tagSearchResults" class="tag-results-container">尚未搜索</div>
                    </div>
                    <div style="flex-grow: 1; display:flex; flex-direction: column; justify-content: flex-end; gap: 8px; padding-bottom:15px;">
                         <button id="copyTagButton" class="panel-button" style="width:100%;">复制标签</button>
                         <button id="fillTagButton" class="panel-button primary" style="width:100%;">填入提示词</button>
                    </div>
                </div>
            </div>

            <div class="settings-group">
                <h3 class="settings-group-title">提示词预设管理</h3>
                <div class="button-group">
                    <label style="margin-bottom:0;">选择预设: <select id="yusheid" style="margin-bottom:0;">
                      ${Object.keys(settings.yushe).map(key => `<option value="${key}" ${settings.yusheid === key ? 'selected' : ''}>${key}</option>`).join('')}
                    </select></label>
                    <button class="panel-button" id="tishici_save_style" title="保存当前风格"><i class="fa-solid fa-save"></i> 保存</button>
                    <button class="panel-button danger" id="tishici_delete_style" title="删除选中风格"><i class="fa-solid fa-trash-can"></i> 删除</button>
                </div>
                <label>正面提示词 (Positive Prompt):</label>
                <textarea id="positivePrompt" placeholder="1girl, solo, beautiful detailed eyes...">${positivePromptValue}</textarea>
                <label>负面提示词 (Negative Prompt):</label>
                <textarea id="negativePrompt" placeholder="low quality, bad anatomy...">${settings.negativePrompt}</textarea>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">快捷预设</h3>
                 <div class="flex-row">
                    <div><label>正面快捷预设: <select id="AQT"><option value="masterpiece,best quality,amazing quality">光辉预设</option><option value="best quality, amazing quality, very aesthetic, absurdres">原版预设</option><option value="">无</option></select></label></div>
                    <div><label>负面快捷预设: <select id="UCP"><option value="bad quality,worst quality,worst detail,sketch,censor,bad anatomy,jpeg artifacts,signature,watermark,old,oldest,conjoined,bad hands">光辉模型预设</option><option value="">无</option></select></label></div>
                </div>
            </div>
        </div>`;
    }

    function createSdPane() {
       return `
        <div id="pane-sd" class="settings-pane">
            <div class="settings-group">
                <h3 class="settings-group-title">API & 模型</h3>
                <label>SD WebUI API地址 (URL):</label>
                <div style="display:flex; gap:10px;">
                  <input type="text" id="sdUrl" value="${settings.sdUrl}" style="margin-bottom:0;">
                  <button id="refreshSdOptions" class="panel-button primary" style="flex-shrink:0;">刷新数据</button>
                </div>
                <div class="flex-row">
                    <div><label>SD 模型 (Checkpoint): <select id="sdModel"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>采样方式 (Sampler): <select id="samplerName"><option value="">-- 点击刷新加载 --</option></select></label></div>
                </div>
                <div class="flex-row">
                    <div><label>调度器 (Scheduler): <select id="sdScheduler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>LoRA 模型: <select id="sdLora" style="width: 100%;"><option value="">-- 点击刷新加载 --</option></select></label></div>
                </div>
                 <label>LoRA 权重:</label>
                 <div style="display:flex; align-items:center; gap:10px;">
                     <input type="range" id="loraWeight" min=0 max=3 step="0.05" value="1.0" style="width:100%; padding:0; margin:0; accent-color: var(--accent-primary);">
                     <span id="loraWeightValue" style="min-width: 35px; font-weight:bold;">1.00</span>
                     <button id="insertLoraBtn" class="panel-button" style="padding: 5px 10px;">填入</button>
                 </div>
            </div>
             <div class="settings-group">
                <h3 class="settings-group-title">高分辨率修复 (Hires. fix)</h3>
                <label style="display:flex; align-items:center; gap: 10px;">
                    <label class="switch"><input type="checkbox" id="enableHr" ${settings.enableHr === 'true' ? 'checked' : ''}><span class="slider"></span></label>
                    <span>启用 Hires. fix</span>
                </label>
                <div class="flex-row">
                    <div><label>放大算法 (Upscaler): <select id="hrUpscaler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>放大倍率 (Scale): <input type="number" id="hrScale" step="0.1" value="${settings.hrScale}"></label></div>
                </div>
                <div class="flex-row">
                    <div><label>重绘幅度 (Denoising): <input type="number" id="hrDenoisingStrength" step="0.05" value="${settings.hrDenoisingStrength}"></label></div>
                    <div><label>Hires Steps: <input type="number" id="hrSecondPassSteps" value="${settings.hrSecondPassSteps}"></label></div>
                </div>
             </div>
             <div class="settings-group">
                <h3 class="settings-group-title">ADetailer</h3>
                 <label style="display:flex; align-items:center; gap: 5px;">启用ADetailer面部修复:
                    <select id="adetailerEnabled" style="width:auto;"><option value="true" ${settings.adetailerEnabled === 'true' ? 'selected' : ''}>启用</option><option value="false" ${settings.adetailerEnabled === 'false' ? 'selected' : ''}>禁用</option></select>
                 </label>
                 <label>ADetailer 模型: <select id="adModel"><option value="face_yolov8n.pt">face_yolov8n.pt</option><option value="face_yolov8s.pt">face_yolov8s.pt</option><option value="face_yolov8m.pt">face_yolov8m.pt</option><option value="hand_yolov8n.pt">hand_yolov8n.pt</option></select></label>
                 <div class="flex-row">
                  <div><label>去噪强度: <input type="number" id="adDenoisingStrength" step="0.1" min=0 max=1 value="${settings.adDenoisingStrength}"></label></div>
                  <div><label>蒙版模糊: <input type="number" id="adMaskBlur" min=0 max="100" value="${settings.adMaskBlur}"></label></div>
                  <div><label>修复填充: <input type="number" id="adInpaintPadding" min=0 max="100" value="${settings.adInpaintPadding}"></label></div>
                </div>
             </div>
            <div class="settings-group">
                <h3 class="settings-group-title">ControlNet</h3>
                <div style="display: flex; justify-content: space-between; align-items: center;">
                    <label style="display:flex; align-items:center; gap: 10px; margin-bottom: 0;">
                        <label class="switch"><input type="checkbox" id="controlNetEnabled" ${settings.controlNetEnabled === 'true' ? 'checked' : ''}><span class="slider"></span></label>
                        <span>启用 ControlNet</span>
                    </label>
                    <button id="open-controlnet-modal-btn" class="panel-button primary"><i class="fa-solid fa-cogs"></i> 配置 ControlNet 单元</button>
                </div>
            </div>
        </div>`;
    }

    function createNaiPane() {
        return `
        <div id="pane-nai" class="settings-pane">
            <div class="settings-group">
                <h3 class="settings-group-title">API 设置</h3>
                <label>渠道: <select id="naiChannel">
                    <option value="proxy" ${settings.naiChannel === 'proxy' ? 'selected' : ''}>第三方代理</option>
                    <option value="official" ${settings.naiChannel === 'official' ? 'selected' : ''}>官方 (Official)</option>
                </select></label>
                <label>API 地址 (URL):</label><input type="text" id="naiApiUrl" value="${settings.naiApiUrl}" data-default-official="https://image.novelai.net/ai/generate-image" data-default-proxy="https://std.loliyc.com/generate">
                <label>Token:</label><input type="text" id="naiToken" value="${settings.naiToken}">
            </div>
            <div class="settings-group">
               <h3 class="settings-group-title">模型与采样</h3>
               <div class="flex-row">
                <div><label>模型 (Model): <select id="naiModel">
                      <option value="nai-diffusion-3" ${settings.naiModel === 'nai-diffusion-3' ? 'selected' : ''}>nai-diffusion-3</option>
                      <option value="nai-diffusion-furry-3" ${settings.naiModel === 'nai-diffusion-furry-3' ? 'selected' : ''}>nai-diffusion-furry-3</option>
                      <option value="nai-diffusion-4-full" ${settings.naiModel === 'nai-diffusion-4-full' ? 'selected' : ''}>nai-diffusion-4-full</option>
                      <option value="nai-diffusion-4-curated-preview" ${settings.naiModel === 'nai-diffusion-4-curated-preview' ? 'selected' : ''}>nai-diffusion-4-curated-preview</option>
                      <option value="nai-diffusion-4-5-full" ${settings.naiModel === 'nai-diffusion-4-5-full' ? 'selected' : ''}>nai-diffusion-4-5-full</option>
                      <option value="nai-diffusion-4-5-curated" ${settings.naiModel === 'nai-diffusion-4-5-curated' ? 'selected' : ''}>nai-diffusion-4-5-curated</option>
                      <option value="nai-diffusio" ${settings.naiModel === 'nai-diffusio' ? 'selected' : ''}>nai-diffusio</option>
                  </select></label></div>
                 <div><label>采样器 (Sampler): <select id="naiSampler">
                      <option value="k_euler_ancestral" ${settings.naiSampler === 'k_euler_ancestral' ? 'selected' : ''}>Euler Ancestral</option>
                      <option value="k_euler" ${settings.naiSampler === 'k_euler' ? 'selected' : ''}>Euler</option>
                      <option value="k_dpmpp_2s_ancestral" ${settings.naiSampler === 'k_dpmpp_2s_ancestral' ? 'selected' : ''}>DMP++2S Ancestral</option>
                      <option value="k_dpmpp_2m_sde" ${settings.naiSampler === 'k_dpmpp_2m_sde' ? 'selected' : ''}>DMP++2M SDE</option>
                      <option value="k_dpmpp_2m" ${settings.naiSampler === 'k_dpmpp_2m' ? 'selected' : ''}>DMP++2M</option>
                      <option value="k_dpmpp_sde" ${settings.naiSampler === 'k_dpmpp_sde' ? 'selected' : ''}>DMP++ SDE</option>
                  </select></label></div>
               </div>
               <label>噪点调度 (Noise Schedule): <select id="naiNoiseSchedule">
                    <option value="native" ${settings.naiNoiseSchedule === 'native' ? 'selected' : ''}>native</option>
                    <option value="exponential" ${settings.naiNoiseSchedule === 'exponential' ? 'selected' : ''}>exponential</option>
                    <option value="polyexponential" ${settings.naiNoiseSchedule === 'polyexponential' ? 'selected' : ''}>polyexponential</option>
                    <option value="karras" ${settings.naiNoiseSchedule === 'karras' ? 'selected' : ''}>karras</option>
                </select></label>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">高级参数 <small style="color:var(--text-secondary)"></small></h3>
                <div class="flex-row">
                    <div><label>SM(nai3生效): <select id="nai3sm">
                        <option value="true" ${settings.nai3sm === 'true' ? 'selected' : ''}>True</option>
                        <option value="false" ${settings.nai3sm === 'false' ? 'selected' : ''}>False</option>
                    </select></label></div>
                    <div><label>SMEA + DYN(nai3生效): <select id="nai3dyn">
                        <option value="true" ${settings.nai3dyn === 'true' ? 'selected' : ''}>True</option>
                        <option value="false" ${settings.nai3dyn === 'false' ? 'selected' : ''}>False</option>
                    </select></label></div>
                </div>
                <div class="flex-row" style="margin-top:15px;">
                     <div><label>多样性(Variety): <select id="nai3Variety">
                        <option value="true" ${settings.nai3Variety === 'true' ? 'selected' : ''}>True</option>
                        <option value="false" ${settings.nai3Variety === 'false' ? 'selected' : ''}>False</option>
                    </select></label></div>
                     <div><label>减少伪影Decrisper(nai3生效): <select id="nai3Deceisp">
                        <option value="true" ${settings.nai3Deceisp === 'true' ? 'selected' : ''}>True</option>
                        <option value="false" ${settings.nai3Deceisp === 'false' ? 'selected' : ''}>False</option>
                    </select></label></div>
                </div>
                <div class="flex-row" style="margin-top:15px;">
                    <div><label>启用角色位置(nai4+生效): <select id="AI_use_coords">
                        <option value="true" ${settings.AI_use_coords === 'true' ? 'selected' : ''}>True</option>
                        <option value="false" ${settings.AI_use_coords === 'false' ? 'selected' : ''}>False</option>
                    </select></label></div>
                    <div></div>
                </div>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">氛围转移 (Vibe Transfer)</h3>
                <label style="display:flex; align-items:center; gap: 10px;">
                    <label class="switch"><input type="checkbox" id="naiVibeTransferEnabled" ${settings.naiVibeTransferEnabled === 'true' ? 'checked' : ''}><span class="slider"></span></label>
                    <span>启用氛围转移 (Vibe Transfer)</span>
                </label>
                <button id="naiVibeUploadBtn" class="panel-button" style="margin-top: 10px; width:auto;">上传参考图 (最多10张)</button>
                <input type="file" id="naiVibeUploadInput" multiple accept="image/*,.naiv4vibe,.json" style="display:none;">
                <p id="naiVibeStatus" class="info" style="font-size: 13px; margin-top: 10px;">未上传图片。提示:V4+模型请上传官网生成的.naiv4vibe,.json文件。</p>
                <div id="naiVibeImageListContainer"></div>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">角色位置 (多角色构图) 使用说明</h3>
                <details>
                    <summary style="cursor: pointer; color: var(--accent-primary);">点击展开/折叠详细说明</summary>
                    <div style="margin-top: 15px; font-size: 0.9em; line-height: 1.6;">
                        <p><strong>注意:</strong> 此功能仅适用于NAI V4及更高版本的模型,并需要在上方【高级参数】中开启 <strong>"启用角色位置"</strong> 选项。</p>
                        <p>在酒馆聊天框中,使用以下特殊格式来精确控制多个角色的位置和特征:</p>
                        <pre style="background-color: var(--bg-input); padding: 10px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word;"><code>Scene Composition: [场景通用标签];
Character 1 Prompt: [角色1的标签] |centers:坐标;
Character 1 UC: [只对角色1生效的负面标签];
Character 2 Prompt: [角色2的标签] |centers:坐标;
Character 2 UC: [只对角色2生效的负面标签];</code></pre>

                        <h4>关键点解释:</h4>
                        <ol style="padding-left: 20px;">
                            <li>
                                <strong><code>Scene Composition:</code></strong>
                                <ul style="padding-left: 20px;">
                                    <li><strong>作用:</strong> 定义整个画面的背景、整体风格、光照和构图。这些标签会影响到所有角色和环境。</li>
                                    <li><strong>示例:</strong> <code>masterpiece, best quality, indoors, library, bookshelf background, dramatic lighting</code></li>
                                </ul>
                            </li>
                            <li>
                                <strong><code>Character X Prompt:</code></strong>
                                <ul style="padding-left: 20px;">
                                    <li><strong>作用:</strong> 只描述这一个角色的所有特征,如性别、服装、发型、动作、表情等。</li>
                                    <li>你可以定义最多4个角色 (<code>Character 1</code> 到 <code>Character 4</code>)。</li>
                                </ul>
                            </li>
                            <li>
                                <strong><code>|centers:XY</code></strong>
                                <ul style="padding-left: 20px;">
                                    <li><strong>作用:</strong> 这是<strong>定位的关键</strong>。它紧跟在角色描述后面,用 <code>|</code> 分隔。</li>
                                    <li><code>X</code> 是字母 A-E (从左到右)。</li>
                                    <li><code>Y</code> 是数字 1-5 (从上到下)。</li>
                                    <li>这会将图像想象成一个 5x5 的网格,你指定一个单元格作为该角色的中心。</li>
                                </ul>
                                <strong>坐标网格示意图:</strong>
                                <pre style="background-color: var(--bg-input); padding: 10px; border-radius: 5px; font-family: monospace;">+---+---+---+---+---+
| A1| B1| C1| D1| E1|  (顶部)
+---+---+---+---+---+
| A2| B2| C2| D2| E2|
+---+---+---+---+---+
| A3| B3| C3| D3| E3|  (中部)
+---+---+---+---+---+
| A4| B4| C4| D4| E4|
+---+---+---+---+---+
| A5| B5| C5| D5| E5|  (底部)
+---+---+---+---+---+
(左侧)          (右侧)</pre>
                                <ul style="padding-left: 20px;">
                                    <li><code>C3</code> 是画面正中心。</li>
                                    <li><code>B3</code> 在画面左半边中间。</li>
                                    <li><code>D3</code> 在画面右半边中间。</li>
                                </ul>
                            </li>
                            <li>
                                <strong><code>Character X UC:</code></strong>
                                <ul style="padding-left: 20px;">
                                    <li><strong>作用:</strong> <strong>极为强大</strong>。这是只针对<strong>这一个角色</strong>的负面提示词 (Undesired Content)。你可以用它来修复单个角色的问题,而不会影响到其他角色。</li>
                                    <li><strong>示例:</strong> 如果角色1出现了多余的手指,你可以在 <code>Character 1 UC:</code> 中加入 <code>bad hands</code>,而不需要污染全局负面提示词。</li>
                                </ul>
                            </li>
                            <li>
                                <strong>分号 <code>;</code></strong>
                                <ul style="padding-left: 20px;"><li>每个定义块(<code>Scene</code>, <code>Character Prompt</code>, <code>UC</code>)都必须以分号结尾,这是代码解析的分隔符。</li></ul>
                            </li>
                        </ol>

                        <hr style="border-color: var(--border-color); margin: 20px 0;">

                        <h4>一个完整的实战例子</h4>
                        <p>假设我们想画一张图:<strong>一个金发蓝眼的女骑士在左边,一个黑发红眼的男法师在右边,背景是奇幻森林。</strong></p>
                        <p>你可以在酒馆聊天框中发送以下内容 (包含在 <code>image###...###</code> 标签内):</p>
                        <pre style="background-color: var(--bg-input); padding: 10px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word;"><code>Scene Composition: masterpiece, best quality, fantasy, enchanted forest, night, glowing mushrooms, god rays, cinematic angle;
Character 1 Prompt: 1girl, blonde hair, blue eyes, beautiful face, full silver armor, knight, holding a greatsword, serious expression |centers:B3;
Character 1 UC: red eyes, black hair, smiling;
Character 2 Prompt: 1boy, black hair, red eyes, handsome face, dark mage robes with gold trim, holding a glowing magic orb, smug smirk |centers:D3;
Character 2 UC: blonde hair, blue eyes, armor;</code></pre>
                    </div>
                </details>
            </div>
        </div>`;
    }

    function createComfyuiPane() {
        const currentWorkflowJSON = settings.comfyuiWorkflows[settings.comfyuiCurrentWorkflow] || "{}";

        let loraControls = '';
        for (let i = 1; i <= 4; i++) {
            const loraId = i === 1 ? 'comfyuiLora' : `comfyuiLora${i}`;
            const strengthId = i === 1 ? 'comfyuiLoraStrength' : `comfyuiLoraStrength${i}`;
            const value = settings[strengthId] || '1.0';

            loraControls += `
                <div class="lora-item">
                     <label>LoRA 模型 ${i}:
                        <select id="${loraId}"><option value="None">-- 点击刷新加载 --</option></select>
                     </label>
                     <div class="lora-strength-control">
                        <input type="range" id="${strengthId}" min=0 max=2 step="0.05" value="${value}">
                        <span class="lora-strength-value">${parseFloat(value).toFixed(2)}</span>
                     </div>
                </div>
            `;
        }

        return `
        <div id="pane-comfyui" class="settings-pane">
            <div class="settings-group">
                <h3 class="settings-group-title">API & 动态参数</h3>
                <label>ComfyUI API地址 (URL):</label>
                <div class="button-group">
                    <input type="text" id="comfyuiUrl" value="${settings.comfyuiUrl}" style="margin-bottom:0;">
                    <button id="refreshComfyuiOptions" class="panel-button primary" style="flex-shrink:0;">刷新数据</button>
                </div>
                 <div class="flex-row">
                    <div><label>主模型 (Checkpoint): <select id="comfyuiModel"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>主采样方式 (Sampler): <select id="comfyuiSampler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                 </div>
                 <div class="flex-row">
                     <div><label>主调度器 (Scheduler): <select id="comfyuiScheduler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                     <div></div>
                </div>
                <div class="lora-grid">${loraControls}</div>
            </div>

             <div class="settings-group">
                <h3 class="settings-group-title">脸部修复 (FaceDetailer) <small style="color:var(--text-secondary)">(仅当工作流包含FaceDetailer节点时生效,face(脸部修复)hand(手部修复))</small></h3>
                <div class="flex-row">
                    <div><label>修复检测类型: <select id="faceDetailerModel"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>修脸步数 (Steps): <input type="number" id="faceDetailerSteps" value="${settings.faceDetailerSteps}"></label></div>
                    <div><label>最小范围 (Guide Size): <input type="number" id="faceDetailerGuideSize" value="${settings.faceDetailerGuideSize}"></label></div>
                    <div><label>最大范围 (Max Size): <input type="number" id="faceDetailerMaxSize" value="${settings.faceDetailerMaxSize}"></label></div>
                </div>
                 <div class="flex-row" style="margin-top:15px;">
                     <div><label>修脸 CFG: <input type="number" id="faceDetailerCfg" step="0.5" value="${settings.faceDetailerCfg}"></label></div>
                     <div><label>修脸采样器 (Sampler): <select id="faceDetailerSampler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                     <div><label>修脸调度器 (Scheduler): <select id="faceDetailerScheduler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                 </div>
            </div>

            <div class="settings-group">
                <h3 class="settings-group-title">高清修复 (Ultimate SD Upscale) <small style="color:var(--text-secondary)">(仅当工作流包含UltimateSDUpscale节点时生效)</small></h3>
                <div class="flex-row">
                    <div><label>修复算法 (Upscale Model): <select id="comfyuiUpscaleModel"><option value="">-- 点击刷新加载 --</option></select></label></div>
                    <div><label>放大倍率 (Upscale By): <input type="number" id="comfyuiUltimateUpscaleBy" step="0.1" value="${settings.comfyuiUltimateUpscaleBy}"></label></div>
                    <div><label>去噪 (Denoise): <input type="number" id="comfyuiUltimateDenoise" step="0.05" value="${settings.comfyuiUltimateDenoise}"></label></div>
                </div>
                <div class="flex-row" style="margin-top:15px;">
                     <div><label>步数 (Steps): <input type="number" id="comfyuiUltimateSteps" value="${settings.comfyuiUltimateSteps}"></label></div>
                     <div><label>提示词相关性 (CFG): <input type="number" id="comfyuiUltimateCfg" step="0.5" value="${settings.comfyuiUltimateCfg}"></label></div>
                     <div><label>模型类型 (Mode Type):
                        <select id="comfyuiUltimateModeType">
                           <option value="Linear" ${settings.comfyuiUltimateModeType === 'Linear' ? 'selected' : ''}>Linear</option>
                           <option value="Chess" ${settings.comfyuiUltimateModeType === 'Chess' ? 'selected' : ''}>Chess</option>
                        </select>
                     </label></div>
                </div>
                 <div class="flex-row" style="margin-top:15px;">
                     <div><label>采样器 (Sampler): <select id="comfyuiUltimateSampler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                     <div><label>调度器 (Scheduler): <select id="comfyuiUltimateScheduler"><option value="">-- 点击刷新加载 --</option></select></label></div>
                     <div></div>
                 </div>
            </div>

            <div class="settings-group">
                <h3 class="settings-group-title">工作流 (Workflow) 管理</h3>
                <label style="display:flex; align-items:center; gap: 10px; margin-bottom: 15px; background-color: rgba(247, 118, 142, 0.1); padding: 8px; border-radius: 5px; border: 1px solid var(--accent-secondary);">
                    <label class="switch"><input type="checkbox" id="comfyuiUseCustomWorkflowOnly" ${settings.comfyuiUseCustomWorkflowOnly === 'true' ? 'checked' : ''}><span class="slider"></span></label>
                    <span><strong style="color:var(--accent-secondary);">启用纯净工作流模式:</strong> 仅使用下方编辑器中的JSON,并只动态注入提示词。</span>
                </label>
                <div class="button-group">
                    <label style="flex-grow: 1;">选择工作流:
                        <select id="comfyuiCurrentWorkflow">
                            ${Object.keys(settings.comfyuiWorkflows).map(name => `<option value="${name}" ${settings.comfyuiCurrentWorkflow === name ? 'selected' : ''}>${name}</option>`).join('')}
                        </select>
                    </label>
                    <button class="panel-button" id="workflow_update_from_params" title="将上方选择的模型、采样器、分辨率等动态参数写入下方的JSON编辑器中,方便您检查。" style="background-color: var(--accent-green); color: var(--bg-main);"><i class="fa-solid fa-download"></i> 检查参数</button>
                    <button class="panel-button" id="workflow_save" title="将当前编辑器中的内容保存为一个新的或覆盖现有的工作流"><i class="fa-solid fa-save"></i> 保存</button>
                    <button class="panel-button danger" id="workflow_delete" title="删除当前选中的工作流"><i class="fa-solid fa-trash-can"></i> 删除</button>
                </div>
                <label>工作流 JSON (可在此编辑):</label>
                <textarea id="comfyuiWorkflowEditor">${currentWorkflowJSON}</textarea>
                <small style="color: var(--text-secondary);">提示:现在动态参数会在生成时<strong style="color: var(--accent-green);">自动应用</strong>,无需依赖此编辑器。点击 <strong style="color: var(--accent-green);"><a><a><a><a><a><a>“检查参数”</a></a></a></a></a></a></strong> 按钮可方便地查看当前参数在工作流中的配置情况。</small>
            </div>
        </div>`;
    }

    function createMiscPane() {
        return `
        <div id="pane-misc" class="settings-pane">
            <div class="settings-group">
                <h3 class="settings-group-title">内容解析</h3>
                <div class="flex-row">
                    <div><label>开始标记 (Start Tag): <input type="text" id="startTag" value="${settings.startTag}"></label></div>
                    <div><label>结束标记 (End Tag): <input type="text" id="endTag" value="${settings.endTag}"></label></div>
                </div>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">自动化与显示</h3>
                <div class="flex-row">
                <div><label>自动点击生成:
                    <select id="zidongdianji">
                    <option value="true" ${settings.zidongdianji === 'true' ? 'selected' : ''}>启用</option>
                    <option value="false" ${settings.zidongdianji === 'false' ? 'selected' : ''}>禁用</option>
                    </select></label>
                </div>
                    <div><label>最大自动点击数: <input type="number" id="maxAutoClicks" min=1 value="${settings.maxAutoClicks}"></label></div>
                </div>
                <div class="flex-row">
                    <div><label>隐藏按钮为双击图片:
                        <select id="dbclike">
                        <option value="true" ${settings.dbclike === 'true' ? 'selected' : ''}>启用</option>
                        <option value="false" ${settings.dbclike === 'false' ? 'selected' : ''}>禁用</option>
                        </select></label>
                    </div>
                    <div><label>兼容前端:
                        <select id="displayMode">
                        <option value=默认 ${settings.displayMode === '默认' ? 'selected' : ''}>默认(快速扫描)</option>
                        <option value="兼容前端" ${settings.displayMode === '兼容前端' ? 'selected' : ''}>深度扫描(匹配大部分前端卡)</option>
                        </select></label>
                    </div>
                </div>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">缓存设置</h3>
                <label>缓存有效期:
                <select id="cache">
                <option value=0 ${settings.cache == 0 ? 'selected' : ''}>不缓存</option>
                <option value=1 ${settings.cache == 1 ? 'selected' : ''}>缓存一天</option>
                <option value=7 ${settings.cache == 7 ? 'selected' : ''}>缓存一星期</option>
                <option value=30 ${settings.cache == 30 ? 'selected' : ''}>缓存一个月</option>
                <option value="365" ${settings.cache == "365" ? 'selected' : ''}>缓存一年</option>
                </select></label>
            </div>
            <div class="settings-group">
                <h3 class="settings-group-title">图片缓存查看</h3>
                <div class="button-group" style="justify-content: space-between; align-items: center; margin-bottom: 10px;">
                    <p id="cache-info-text" style="margin: 0;">点击刷新加载缓存...</p>
                    <div style="display: flex; gap: 8px;">
                        <button id="download-cache-page" class="panel-button">
                            <i class="fa-solid fa-file-arrow-down"></i> 下载本页
                        </button>
                        <button id="download-cache-all" class="panel-button">
                            <i class="fa-solid fa-archive"></i> 下载全部
                        </button>
                        <button id="refresh-cache-view" class="panel-button">
                            <i class="fa-solid fa-sync"></i> 刷新
                        </button>
                    </div>
                </div>
                <div id="cache-viewer-grid" class="cache-grid">
                    <!-- 缩略图将在这里动态生成 -->
                </div>
                <div id="cache-viewer-pagination" class="pagination">
                    <!-- 分页控件将在这里动态生成 -->
                </div>
            </div>
        </div>`;
    }
    async function downloadImagesAsZip(imageItems, zipFileName) {
        if (!imageItems || imageItems.length === 0) {
            showToast('没有可下载的图片。', 'error');
            return;
        }

        const zip = new JSZip();
        showToast(`正在打包 ${imageItems.length} 张图片...`, 'success', 10000); // 显示一个较长时间的提示

        for (const item of imageItems) {
            // 确保 imageData 存在且是 base64 格式
            if (item.imageData && item.imageData.includes(',')) {
                const base64Data = item.imageData.split(',')[1];
                // 使用图片ID作为文件名,保证唯一性
                zip.file(`image-${item.id}.png`, base64Data, { base64: true });
            }
        }

        try {
            const content = await zip.generateAsync({ type: "blob" });

            showToast('打包完成,即将开始下载!', 'success');

            // 创建一个隐藏的链接来触发浏览器下载
            const link = document.createElement('a');
            link.href = URL.createObjectURL(content);
            link.download = zipFileName;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link); // 清理DOM
            URL.revokeObjectURL(link.href); // 释放内存

        } catch (error) {
            console.error("生成ZIP文件失败:", error);
            showToast('打包图片失败,请查看控制台。', 'error');
        }
    }
    let sortedCache = [];
    let currentCachePage = 1;
    const ITEMS_PER_PAGE = 12; // 每页显示12张图

    async function initCacheViewer() {
        const infoText = document.getElementById('cache-info-text');
        if (!infoText) return;
        infoText.textContent = '正在加载缓存数据...';

        try {
            // 从IndexedDB直接获取所有图片记录
            const allImages = await StoreGetAll();
            if (!allImages || allImages.length === 0) {
                infoText.textContent = '缓存为空。';
                document.getElementById('cache-viewer-grid').innerHTML = '';
                document.getElementById('cache-viewer-pagination').innerHTML = '';
                sortedCache = [];
                return;
            }

            // 按时间戳降序排序(最新的在前)
            sortedCache = allImages.sort((a, b) => b.timestamp - a.timestamp);

            currentCachePage = 1;
            renderCachePage(currentCachePage);
        } catch(error) {
            console.error("加载缓存视图失败:", error);
            infoText.textContent = '加载缓存失败。';
        }
    }

    async function renderCachePage(page) {
        currentCachePage = page;
        const grid = document.getElementById('cache-viewer-grid');
        const infoText = document.getElementById('cache-info-text');
        if (!grid || !infoText) return;

        grid.innerHTML = '<i>正在渲染图片...</i>';

        const totalItems = sortedCache.length;
        const totalPages = Math.ceil(totalItems / ITEMS_PER_PAGE);
        const startIndex = (page - 1) * ITEMS_PER_PAGE;
        const endIndex = Math.min(startIndex + ITEMS_PER_PAGE, totalItems);

        infoText.textContent = `共 ${totalItems} 张图片,正在显示 ${startIndex + 1}-${endIndex} 张`;

        const pageItems = sortedCache.slice(startIndex, endIndex);
        grid.innerHTML = ''; // 清空,准备填充

        if (pageItems.length === 0 && totalItems > 0) {
            // 如果当前页没有项目了(例如,删除了最后一页的所有项目),则跳到前一页
            renderCachePage(Math.max(1, page - 1));
            return;
        }

        // 现在item直接就是完整的图片对象,无需再次查询
        for (const item of pageItems) {
            // 【已修复】 item.imageData 是正确的图片数据来源
            if (item && item.imageData) {
                const cacheItemDiv = document.createElement('div');
                cacheItemDiv.className = 'cache-item';
                cacheItemDiv.dataset.id = item.id;

                // 【核心修复】创建并设置 img 标签,然后将其添加到 cacheItemDiv
                const img = document.createElement('img');
                img.src = item.imageData;
                img.alt = "Cached Image Thumbnail";
                cacheItemDiv.appendChild(img);

                const deleteBtn = document.createElement('button');
                deleteBtn.className = 'delete-cache-btn';
                deleteBtn.title = '删除此记录';
                deleteBtn.innerHTML = '×';
                cacheItemDiv.appendChild(deleteBtn);

                grid.appendChild(cacheItemDiv);

                // 添加事件监听
                img.addEventListener('dblclick', () => showImageLightbox(item.imageData, item.id));
                deleteBtn.addEventListener('click', (e) => {
                    e.stopPropagation(); // 防止触发双击事件
                    deleteSingleCacheItem(item.id, cacheItemDiv);
                });
            }
        }

        renderPaginationControls(page, totalPages);
    }


    function renderPaginationControls(currentPage, totalPages) {
        const paginationContainer = document.getElementById('cache-viewer-pagination');
        if (!paginationContainer) return;
        paginationContainer.innerHTML = '';

        if (totalPages <= 1) return;

        const prevButton = document.createElement('button');
        prevButton.textContent = '上一页';
        prevButton.className = 'panel-button';
        prevButton.disabled = currentPage === 1;
        prevButton.addEventListener('click', () => renderCachePage(currentPage - 1));
        paginationContainer.appendChild(prevButton);

        const pageInfo = document.createElement('span');
        pageInfo.className = 'page-info';
        pageInfo.textContent = `第 ${currentPage} / ${totalPages} 页`;
        paginationContainer.appendChild(pageInfo);

        const nextButton = document.createElement('button');
        nextButton.textContent = '下一页';
        nextButton.className = 'panel-button';
        nextButton.disabled = currentPage === totalPages;
        nextButton.addEventListener('click', () => renderCachePage(currentPage + 1));
        paginationContainer.appendChild(nextButton);
    }

    async function deleteSingleCacheItem(id, elementToRemove) {
        const imageId = Number(id); // ID现在是数字
        if (await stylishConfirm("确定要永久删除这张图片缓存吗?")) {
            try {
                // 1. 从IndexedDB删除图片记录
                await Storedelete(imageId);

                // 2. 从内存中的排序数组删除
                sortedCache = sortedCache.filter(item => item.id !== imageId);

                // 3. 从位置映射中移除对此ID的引用
                let mapUpdated = false;
                for (const locationHash in locationToImageIdMap) {
                    if (locationToImageIdMap[locationHash] === imageId) {
                        delete locationToImageIdMap[locationHash];
                        mapUpdated = true;
                    }
                }

                // 4. 如果映射有更新,则写回数据库
                if (mapUpdated) {
                    await Storereadwrite({ id: 'locationMap', data: locationToImageIdMap });
                }

                showToast(`图片记录 (ID: ${imageId}) 已删除。`);

                // 5. 重新渲染当前页
                renderCachePage(currentCachePage);

            } catch (error) {
                console.error("删除单个缓存项目失败:", error);
                alert("删除失败,详情请查看控制台。");
            }
        }
    }

    function showImageLightbox(base64Data, id) {
        // 防止重复创建
        const existingLightbox = document.querySelector('.lightbox-overlay');
        if (existingLightbox) {
            existingLightbox.remove();
        }

        const overlay = document.createElement('div');
        overlay.className = 'lightbox-overlay';

        const downloadFilename = `image-${id}.png`;

        overlay.innerHTML = `
        <div class="lightbox-content">
             <img src="${base64Data}" alt="preview" class="lightbox-img" />
            <a href="${base64Data}" class="lightbox-download" download="${downloadFilename}" title="下载原图">
                <i class="fa-solid fa-download"></i> 下载
            </a>
            <button class="lightbox-close" title="关闭 (Esc)">
                <i class="fa-solid fa-times"></i> 关闭
            </button>
        </div>
    `;
        document.body.appendChild(overlay);

        const close = () => {
            if (overlay.parentNode) {
                overlay.parentNode.removeChild(overlay);
            }
            document.removeEventListener('keydown', escListener);
        };

        overlay.addEventListener('click', (e) => {
            // 如果点击的是背景遮罩层,而不是图片或按钮,则关闭
            if (e.target === overlay) {
                close();
            }
        });

        overlay.querySelector('.lightbox-close').addEventListener('click', close);

        // 添加键盘Esc键关闭功能
        const escListener = (e) => {
            if (e.key === 'Escape') {
                close();
            }
        };
        document.addEventListener('keydown', escListener);
    }

    function openControlNetModal() {
        // 创建一个临时状态,以便用户可以取消更改
        let tempUnits = JSON.parse(JSON.stringify(settings.controlNetUnits || []));

        // 创建模态框的HTML结构
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'controlnet-modal-overlay';
        // 使用主面板的类名以实现样式统一
        modalOverlay.innerHTML = `
            <div class="controlnet-modal settings-panel-reborn">
                <div class="panel-header">
                    <h2>配置 ControlNet</h2>
                    <button id="add-cn-unit-modal-btn" class="panel-button primary"><i class="fa-solid fa-plus"></i> 新增单元</button>
                </div>
                <div id="controlnet-modal-content" class="controlnet-modal-content">
                    <!-- 单元将在这里动态生成 -->
                </div>
                <div class="panel-footer">
<span id="cn-unit-count-display" class="unit-count" style="color: var(--text-secondary); font-size: 0.9em;"></span>
                    <div class="footer-buttons">
                        <button id="cancel-cn-modal-btn" class="panel-button">取消</button>
                        <button id="save-cn-modal-btn" class="panel-button primary">保存并关闭</button>
                    </div>
                </div>
            </div>
        `;
        document.body.appendChild(modalOverlay);

        const modal = modalOverlay.querySelector('.controlnet-modal');
        const contentContainer = modal.querySelector('#controlnet-modal-content');

        // --- 核心逻辑函数 ---

        // 渲染函数:根据 tempUnits 数组更新UI
        function renderUnits() {
            contentContainer.innerHTML = ''; // 清空
            tempUnits.forEach((unit, index) => {
                contentContainer.innerHTML += createControlNetUnitHTML(unit, index);
            });
            populateControlNetSelects(); // 填充模型和预处理器
            // 恢复已保存的值
            tempUnits.forEach((unit, index) => {
                // 使用新的选择器 .settings-group
                const unitEl = contentContainer.querySelector(`.settings-group[data-index="${index}"]`);
                if (unitEl) {
                    if (unit.model) unitEl.querySelector('.cn-model').value = unit.model;
                    if (unit.module) unitEl.querySelector('.cn-module').value = unit.module;
                    if (unit.resize_mode) unitEl.querySelector('.cn-resize_mode').value = unit.resize_mode;
                    if (unit.control_mode) unitEl.querySelector('.cn-control_mode').value = unit.control_mode;
                    unitEl.querySelector('.cn-pixel_perfect').checked = (unit.pixel_perfect === true || unit.pixel_perfect === 'true');
                }
            });
            // 更新计数
            modal.querySelector('#cn-unit-count-display').textContent = `当前单元数: ${tempUnits.length}/4`;
            modal.querySelector('#add-cn-unit-modal-btn').style.display = tempUnits.length >= 4 ? 'none' : 'inline-flex';
        }

        function saveAndClose() {
             // 从UI读取当前配置到 tempUnits
            const updatedUnits = [];
            // 使用新的选择器 .settings-group
            contentContainer.querySelectorAll('.settings-group').forEach(unitEl => {
                const imageEl = unitEl.querySelector('.cn-preview-img');
                updatedUnits.push({
                    enabled: unitEl.querySelector('.cn-enabled').checked,
                    image: imageEl ? imageEl.src : null,
                    model: unitEl.querySelector('.cn-model').value,
                    module: unitEl.querySelector('.cn-module').value,
                    resize_mode: unitEl.querySelector('.cn-resize_mode').value,
                    control_mode: unitEl.querySelector('.cn-control_mode').value,
                    pixel_perfect: unitEl.querySelector('.cn-pixel_perfect').checked,
                    weight: 1.0, lowvram: false, processor_res: 512, threshold_a: 64, threshold_b: 64, guidance_start: 0.0, guidance_end: 1.0,
                });
            });
            tempUnits = updatedUnits;

            // 将临时状态正式应用到全局设置
            settings.controlNetUnits = tempUnits;
            closeModal();
showToast('ControlNet 配置已保存!');
        }

        // 取消并关闭
        function closeModal() {
            modal.classList.remove('visible');
            setTimeout(() => {
                if (modalOverlay.parentNode) {
                   document.body.removeChild(modalOverlay);
                }
            }, 200); // 等待动画完成
        }

        // --- 事件绑定 ---
        modal.querySelector('#add-cn-unit-modal-btn').addEventListener('click', () => {
            if (tempUnits.length < 4) {
                tempUnits.push({ enabled: true, image: null, module: 'none', model: 'None', resize_mode: 1, control_mode: 0, pixel_perfect: false });
                renderUnits();
            }
        });

        contentContainer.addEventListener('click', e => {
            // 使用新的选择器 .settings-group
            const unitEl = e.target.closest('.settings-group');
            if (!unitEl) return;
            const index = parseInt(unitEl.dataset.index, 10);
            if (e.target.closest('.remove-cn-unit')) {
                tempUnits.splice(index, 1);
                renderUnits();
            } else if (e.target.closest('.cn-image-upload-wrapper')) {
                unitEl.querySelector('.cn-image-input').click();
            }
        });

        contentContainer.addEventListener('change', e => {
             const input = e.target;
             if (input.classList.contains('cn-image-input') && input.files[0]) {
                 const file = input.files[0];
                 const reader = new FileReader();
                 // 使用新的选择器 .settings-group
                 const unitEl = input.closest('.settings-group');
                 const index = parseInt(unitEl.dataset.index, 10);
                 reader.onload = (loadEvent) => {
                     const base64 = loadEvent.target.result;
                     if(tempUnits[index]) tempUnits[index].image = base64;
                     renderUnits(); // 重新渲染以显示预览
                 };
                 reader.readAsDataURL(file);
             }
        });

        modal.querySelector('#save-cn-modal-btn').addEventListener('click', saveAndClose);
        modal.querySelector('#cancel-cn-modal-btn').addEventListener('click', closeModal);

        // 初始化
        renderUnits();
        setTimeout(() => modal.classList.add('visible'), 20); // 延迟添加以触发过渡动画
    }


    // -- Danbooru 标签搜索功能 ---
    async function searchDanbooruTags() {
        const query = document.getElementById('tagSearchInput').value.trim();
        const resultsContainer = document.getElementById('tagSearchResults');
        if (!query) {
            resultsContainer.textContent = '请输入搜索关键词';
            return;
        }

        resultsContainer.innerHTML = '<i>正在搜索...</i>';

        const delay = 1000 + Math.random() * 2000;
        await new Promise(resolve => setTimeout(resolve, delay));

        const searchUrl = `https://danbooru.donmai.us/autocomplete?search%5Bquery%5D=${encodeURIComponent(query)}&search%5Btype%5D=tag_query&version=1&limit=10`;

        try {
            const response = await gmXmlHttpRequestPromise({
                method: "GET",
                url: searchUrl,
                headers: {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
                    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                    "Referer": "https://danbooru.donmai.us/",
                    "X-Requested-With": "XMLHttpRequest"
                },
                anonymous: false
            });

            const responseText = response.responseText;

            if (isCloudflareChallenge(responseText)) {
                handleCloudflareBlock(resultsContainer, query);
                return;
            }

            const parser = new DOMParser();
            const doc = parser.parseFromString(responseText, "text/html");

            if (doc.title && doc.title.includes("Just a moment...")) {
                 handleCloudflareBlock(resultsContainer, query);
                 return;
            }


            const listItems = doc.querySelectorAll("li[data-autocomplete-value]");
            const tagsArray = [];
            listItems.forEach(li => {
                // .label 是显示出来的文本,如 "原神→genshin impact"
                const label = li.querySelector('a')?.textContent.trim() || '';
                // .value 是实际的tag,如 "genshin_impact"
                const value = li.dataset.autocompleteValue;
                const post_count = li.querySelector('.post-count')?.textContent.trim() || '0';

                // 构建成与原函数兼容的对象结构
                if(value) {
                  tagsArray.push({ label, value, post_count });
                }
            });

            // 将构建好的数组传递给渲染函数,无需修改渲染函数
            renderResults(tagsArray, query, resultsContainer);

        } catch (error) {
            console.error('请求失败:', error);
            resultsContainer.innerHTML = `
            <div class="error">请求异常: ${error.status || '未知错误'}</div>
            <button class="retry-btn">重试</button>
        `;
            resultsContainer.querySelector('.retry-btn').addEventListener('click', searchDanbooruTags);
        }
    }





    // 检测Cloudflare拦截
    function isCloudflareChallenge(text) {
        return /<title>Just a moment...<\/title>|cloudflare/i.test(text);
    }

    // 处理Cloudflare拦截
    function handleCloudflareBlock(container, query) {
        container.innerHTML = `
        <div class="cf-warning">
            <h4>⚠️ Cloudflare拦截检测</h4>
            <p>需要完成人机验证才能访问Danbooru:</p>
            <ol>
                <li><a href="https://danbooru.donmai.us/" target="_blank">点击此处打开新标签页</a></li>
                <li>完成"Just a moment..."验证</li>
                <li>返回此页面点击重试</li>
            </ol>
            <div class="actions">
                <button class="retry-btn">我已验证,重试搜索</button>
                <button class="direct-link" data-query="${encodeURIComponent(query)}">直接打开搜索结果</button>
            </div>
        </div>
    `;

        // 添加重试按钮事件
        container.querySelector('.retry-btn').addEventListener('click', searchDanbooruTags);

        // 添加直接打开链接事件
        container.querySelector('.direct-link').addEventListener('click', function() {
            window.open(`https://danbooru.donmai.us/posts?tags=${this.dataset.query}`, '_blank');
        });
    }

    // 渲染结果
    function renderResults(tagsArray, query, container) {
        container.innerHTML = '';

        if (tagsArray.length === 0) {
            container.textContent = '无搜索结果';
            return;
        }

        tagsArray.forEach(tagObject => {
            const item = document.createElement('div');
            item.className = 'tag-result-item';
            // 使用'value'作为dataset.tag,这是实际的英文tag
            item.dataset.tag = tagObject.value;

            const tagName = document.createElement('span');
            tagName.textContent = tagObject.label;
            item.appendChild(tagName);

            const postCountSpan = document.createElement('span');
            postCountSpan.className = 'tag-post-count';
            postCountSpan.textContent = formatCount(tagObject.post_count);
            item.appendChild(postCountSpan);

            item.addEventListener('click', () => toggleSelection(container, item));
            container.appendChild(item);
        });
    }

    // 辅助函数
    function toggleSelection(container, item) {
        container.querySelectorAll('.tag-result-item').forEach(el => {
            el.classList.remove('selected');
        });
        item.classList.add('selected');
    }

    function formatCount(count) {
        if (!count) return '0';
        return count > 1000 ? `${(count/1000).toFixed(1)}k` : count;
    }
    function copySelectedTag() {
        const selectedItem = document.querySelector('#tagSearchResults .selected');

        if (!selectedItem) {
            showToast('请先选择一个标签', 'error'); // 使用Toast提示错误
            return;
        }

        // 从 data-tag 获取原始tag, 并替换下划线为空格
        const tagToCopy = selectedItem.dataset.tag.replace(/_/g, ' ');

        navigator.clipboard.writeText(tagToCopy).then(() => {
            showToast(`标签 "${tagToCopy}" 已复制!`); // 使用Toast提示成功
        }).catch(err => {
            console.error('复制失败:', err);
            showToast('复制失败, 请查看控制台', 'error'); // 使用Toast提示失败
        });
    }
// ... (后略)





    function fillSelectedTag() {
        const selectedItem = document.querySelector('#tagSearchResults .selected');
        const positivePromptTextarea = document.getElementById('positivePrompt');

        if (!selectedItem) {
            alert('请先从搜索结果中选择一个标签。');
            return;
        }

        // 从 data-tag 获取原始tag, 并替换下划线为空格
        const tagToFill = selectedItem.dataset.tag.replace(/_/g, ' ');

        if (positivePromptTextarea.value.trim() !== '' && !positivePromptTextarea.value.endsWith(',')) {
            positivePromptTextarea.value += ', ';
        }
        positivePromptTextarea.value += tagToFill + ',';
    }


    function updateWorkflowFromSettings() {
        const editor = document.getElementById("comfyuiWorkflowEditor");
        if (!editor) {
            alert("错误:找不到工作流编辑器。");
            return;
        }

        let workflow;
        try {
            workflow = JSON.parse(editor.value);
        } catch (e) {
            alert("无法更新,因为编辑器中的JSON格式无效。请先修正。");
            return;
        }

        console.log("[ComfyUI] 开始从设置面板更新工作流JSON...");
        const log = [];

        // 辅助函数: 优先按标题搜索,失败则按类型搜索
        const findNode = (wf, titles, classType) => {
            for (const title of titles) {
                for (const id in wf) {
                    if (wf[id]?._meta?.title?.includes(title)) {
                         log.push(`✅ 按标题 "${title}" 找到节点 #${id} (${wf[id].class_type})`);
                         return [id];
                    }
                }
            }
            const ids = [];
            for (const id in wf) {
                if (wf[id]?.class_type === classType) {
                    ids.push(id);
                }
            }
            if(ids.length > 0) log.push(`⚠️ 按标题未找到,改用类型 "${classType}" 找到节点 #${ids.join(', ')}`);
            else log.push(`❌ 未找到任何匹配 "${titles.join('/')}" 或类型 "${classType}" 的节点`);
            return ids;
        };
        const findNodeByTitle = (title) => {
             for (const id in workflow) {
                 if(workflow[id]?._meta?.title?.includes(title)) return id;
             }
             return null;
        }


        // 获取所有动态参数的值 (从UI界面直接获取)
        const params = {
            width: document.getElementById("width").value,
            height: document.getElementById("height").value,
            steps: document.getElementById("steps").value,
            cfg: document.getElementById("sdCfgScale").value,
            model: document.getElementById("comfyuiModel").value,
            sampler: document.getElementById("comfyuiSampler").value,
            scheduler: document.getElementById("comfyuiScheduler").value,
            loras: [
                { name: document.getElementById("comfyuiLora").value, strength: document.getElementById("comfyuiLoraStrength").value },
                { name: document.getElementById("comfyuiLora2").value, strength: document.getElementById("comfyuiLoraStrength2").value },
                { name: document.getElementById("comfyuiLora3").value, strength: document.getElementById("comfyuiLoraStrength3").value },
                { name: document.getElementById("comfyuiLora4").value, strength: document.getElementById("comfyuiLoraStrength4").value }
            ],
            faceDetailer: {
                steps: document.getElementById("faceDetailerSteps").value,
                guide_size: document.getElementById("faceDetailerGuideSize").value,
                max_size: document.getElementById("faceDetailerMaxSize").value,
                cfg: document.getElementById("faceDetailerCfg").value,
                sampler_name: document.getElementById("faceDetailerSampler").value,
                model_name: document.getElementById("faceDetailerModel").value,
                scheduler: document.getElementById("faceDetailerScheduler").value
            }
        };

        // 1. 注入分辨率
        const resNodeIds = findNode(workflow, ["分辨率", "Latent"], "EmptyLatentImage");
        if (resNodeIds.length > 0) {
            resNodeIds.forEach(id => {
                workflow[id].inputs.width = Number(params.width);
                workflow[id].inputs.height = Number(params.height);
            });
        }

        // 2. 注入采样器 (KSampler) 参数
        const samplerNodeIds = findNode(workflow, ["采样器", "Sampler"], "KSampler");
        if (samplerNodeIds.length > 0) {
            samplerNodeIds.forEach(id => {
                workflow[id].inputs.steps = Number(params.steps);
                workflow[id].inputs.cfg = Number(params.cfg);
                workflow[id].inputs.sampler_name = params.sampler;
                workflow[id].inputs.scheduler = params.scheduler;
            });
        }

        // 3. 注入主模型
        const ckptNodeIds = findNode(workflow, ["主模型", "Checkpoint"], "CheckpointLoaderSimple");
        if (ckptNodeIds.length > 0) {
            ckptNodeIds.forEach(id => workflow[id].inputs.ckpt_name = params.model);
        }

        // 4. 注入 LoRA 参数
        const loraStackNodeIds = findNode(workflow, ["Lora Loader Stack"], "Lora Loader Stack (rgthree)");
        if (loraStackNodeIds.length > 0) {
             const loraStackNodeId = loraStackNodeIds[0]; // 通常只有一个,我们操作第一个找到的
             // 日志已经在 findNode 函数中生成,这里不再重复
             workflow[loraStackNodeId].inputs.lora_01 = params.loras[0].name;
             workflow[loraStackNodeId].inputs.strength_01 = parseFloat(params.loras[0].strength);
             workflow[loraStackNodeId].inputs.lora_02 = params.loras[1].name;
             workflow[loraStackNodeId].inputs.strength_02 = parseFloat(params.loras[1].strength);
             workflow[loraStackNodeId].inputs.lora_03 = params.loras[2].name;
             workflow[loraStackNodeId].inputs.strength_03 = parseFloat(params.loras[2].strength);
             workflow[loraStackNodeId].inputs.lora_04 = params.loras[3].name;
             workflow[loraStackNodeId].inputs.strength_04 = parseFloat(params.loras[3].strength);
        } else {
            log.push("⚠️ 未找到 'Lora Loader Stack' 标题的节点,LoRA参数未注入。");
        }

        // 5. 注入 FaceDetailer 参数
        const faceDetailerNodeIds = findNode(workflow, ["FaceDetailer"], "FaceDetailer");
        if (faceDetailerNodeIds.length > 0) {
            faceDetailerNodeIds.forEach(id => {
                workflow[id].inputs.steps = Number(params.faceDetailer.steps);
                workflow[id].inputs.guide_size = Number(params.faceDetailer.guide_size);
                workflow[id].inputs.max_size = Number(params.faceDetailer.max_size);
                workflow[id].inputs.cfg = Number(params.faceDetailer.cfg);
                workflow[id].inputs.sampler_name = params.faceDetailer.sampler_name;
                workflow[id].inputs.scheduler = params.faceDetailer.scheduler;
            });
        }
        // 6. 注入脸部修复检测模型 (UltralyticsDetectorProvider)
        const detectorNodeIds = findNode(workflow, ["UltralyticsDetectorProvider"], "UltralyticsDetectorProvider");
        if (detectorNodeIds.length > 0) {
            detectorNodeIds.forEach(id => {
                 workflow[id].inputs.model_name = params.faceDetailer.model_name;
            });
            log.push(`✅ 按类型 "UltralyticsDetectorProvider" 找到节点 #${detectorNodeIds.join(', ')} 并更新模型`);
        } else {
            log.push(`⚠️ 未找到任何 "UltralyticsDetectorProvider" 类型的节点,修脸模型未注入。`);
        }
        editor.value = JSON.stringify(workflow, null, 2);
        console.log("[ComfyUI] 更新日志:\n" + log.join('\n'));
        alert("工作流已根据当前动态参数更新!\n这仅用于检查,实际生成时会自动应用已保存的最新设置。"
        );
    }

    function createVibeThumbnail(imageData) {
        const container = document.getElementById('naiVibeImageListContainer');
        if (!container) return;

        const timestamp = Date.now() + Math.random();
        const item = document.createElement('div');
        item.className = 'vibe-item';
        item.dataset.id = timestamp;

        let thumbnailUrl, infoExtract, refStrength;

        // 根据传入数据类型进行处理
        if (imageData.type === 'vibeFile' && imageData.vibeData) {
            // 这是 .naiv4vibe 文件
            const vibe = imageData.vibeData;
            thumbnailUrl = vibe.thumbnail; // 使用文件内置的缩略图
            infoExtract = vibe.importInfo?.information_extracted || '0.7';
            refStrength = vibe.importInfo?.strength || '0.5';
            item.dataset.type = 'vibeFile';
            // 将完整的 vibe JSON 数据存储在 DOM 元素上
            item.dataset.vibeData = JSON.stringify(vibe);
            item.title = `Vibe 文件: ${vibe.name}`;
        } else if (imageData.type === 'image') {
            // 这是普通图片 (用于v3或代理)
            thumbnailUrl = imageData.base64;
            infoExtract = imageData.infoExtract || '1.0';
            refStrength = imageData.refStrength || '0.5';
            item.dataset.type = 'image';
            // 将图片的 base64 存储起来
            item.dataset.base64 = imageData.base64;
            item.title = "普通参考图 (用于 V3 / 代理)";
        } else {
            console.error("创建Vibe缩略图失败:未知的数据类型", imageData);
            return;
        }


        item.innerHTML = `
            <button class="vibe-delete-btn" title="删除图片">×</button>
            <img src="${imageData.type === 'vibeFile' ? thumbnailUrl : imageData.base64}" alt="Vibe Transfer Image">
            <div class="controls">
                <div class="slider-group">
                    <div class="slider-label">
                        <span>信息提取度</span>
                        <span class="slider-value">${parseFloat(infoExtract).toFixed(2)}</span>
                    </div>
                    <input type="range" class="info-extract-slider" min="0.01" max=1 step="0.01" value="${infoExtract}">
                </div>
                <div class="slider-group">
                    <div class="slider-label">
                        <span>参考强度</span>
                        <span class="slider-value">${parseFloat(refStrength).toFixed(2)}</span>
                    </div>
                    <input type="range" class="ref-strength-slider" min="0.01" max=1 step="0.01" value="${refStrength}">
                </div>
            </div>
        `;

        item.querySelector('.vibe-delete-btn').addEventListener('click', (e) => {
            e.target.closest('.vibe-item').remove();
            updateVibeStatus();
        });

        const sliders = item.querySelectorAll('input[type="range"]');
        sliders.forEach(slider => {
            slider.addEventListener('input', (e) => {
                e.target.previousElementSibling.querySelector('.slider-value').textContent = parseFloat(e.target.value).toFixed(2);
            });
        });

        container.appendChild(item);
        updateVibeStatus();
    }

    function updateVibeStatus() {
        const container = document.getElementById('naiVibeImageListContainer');
        const statusEl = document.getElementById('naiVibeStatus');
        if (!container || !statusEl) return;

        const count = container.children.length;
        statusEl.textContent = `已上传 ${count}/10 张图片。`;
        statusEl.className = 'info';
    }

    function handleVibeImageUpload(event) {
        const files = event.target.files;
        const statusEl = document.getElementById('naiVibeStatus');
        const container = document.getElementById('naiVibeImageListContainer');
        if (!files.length || !statusEl || !container) return;

        const currentCount = container.children.length;
        const limit = 10;
        if (currentCount + files.length > limit) {
            statusEl.textContent = `超出数量限制!最多上传 ${limit} 张图片,您还可以上传 ${limit - currentCount} 张。`;
            statusEl.className = 'error';
            return;
        }

        Array.from(files).forEach(file => {
            const reader = new FileReader();

            if (file.name.toLowerCase().endsWith('.naiv4vibe') || file.name.toLowerCase().endsWith('.json')) {
                // 处理 .naiv4vibe 文件
                reader.onload = (e) => {
                    try {
                        const vibeData = JSON.parse(e.target.result);
                        if (vibeData.identifier === "novelai-vibe-transfer") {
                            createVibeThumbnail({ type: 'vibeFile', vibeData: vibeData });
                        } else {
                            alert(`文件 "${file.name}" 不是一个有效的 .naiv4vibe 文件。`);
                        }
                    } catch (err) {
                        console.error("解析 .naiv4vibe 文件失败:", err);
                        alert(`解析文件 "${file.name}" 失败,请确保它是一个有效的JSON文件。`);
                    }
                };
                reader.readAsText(file);
            } else if (file.type.startsWith('image/')) {
                // 处理普通图片文件
                reader.onload = (e) => {
                    createVibeThumbnail({ type: 'image', base64: e.target.result });
                };
                reader.readAsDataURL(file);
            } else {
                 alert(`不支持的文件类型: ${file.name}`);
            }
        });

        event.target.value = '';
    }


      function gmXmlHttpRequestPromise(details) {
          return new Promise((resolve, reject) => {
              details.timeout = details.timeout || 300000; // 300000毫秒 = 5分钟
              details.onload = resolve;
              details.onerror = reject;
              details.ontimeout = reject; // 明确处理超时事件
              GM_xmlhttpRequest(details);
          });
      }

      async function fetchSdOptions(showAlertOnSuccess = true) {
          const sdUrl = removeTrailingSlash(document.getElementById('sdUrl').value);
          if (!sdUrl) {
              if (showAlertOnSuccess) alert('请先输入有效的 SD URL。');
              return;
          }

          const modelsSelect = document.getElementById('sdModel');
          const samplersSelect = document.getElementById('samplerName');
          const upscalersSelect = document.getElementById('hrUpscaler');
          const lorasSelect = document.getElementById('sdLora');
          const sdSchedulerSelect = document.getElementById('sdScheduler');
          const cnContainer = document.getElementById('controlnet-units-container');

          const setLoading = (select) => { if (select) select.innerHTML = '<option>加载中...</option>'; };
          [modelsSelect, samplersSelect, upscalersSelect, lorasSelect, sdSchedulerSelect].forEach(setLoading);
          if (cnContainer) cnContainer.innerHTML = '<p style="color: var(--text-secondary);">正在加载 ControlNet 数据...</p>';

          const results = await Promise.allSettled([
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/sdapi/v1/sd-models` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/sdapi/v1/samplers` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/sdapi/v1/schedulers` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/sdapi/v1/upscalers` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/sdapi/v1/loras` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/controlnet/model_list` }),
              gmXmlHttpRequestPromise({ method: "GET", url: `${sdUrl}/controlnet/module_list` })
          ]);

          const [
              modelsResult,
              samplersResult,
              schedulersResult,
              upscalersResult,
              lorasResult,
              cnModelsResult,
              cnModulesResult
          ] = results;

          const populateSelect = (select, items, valueField, textField) => {
              if (!select) return;
              select.innerHTML = '';
              items.forEach(item => {
                  const option = new Option(item[textField], item[valueField]);
                  select.add(option);
              });
          };

          const setError = (select, savedValueKey) => {
              if (select) {
                  select.innerHTML = `<option value="">刷新失败</option>`;
                  const savedValue = settings[savedValueKey];
                  if(savedValue){
                      select.add(new Option(savedValue, savedValue, true, true));
                  }
              }
          };

          if (modelsResult.status === 'fulfilled') {
              populateSelect(modelsSelect, JSON.parse(modelsResult.value.responseText), 'title', 'title');
              modelsSelect.value = settings.sdModel;
          } else {
              console.error('获取SD模型失败:', modelsResult.reason);
              setError(modelsSelect, 'sdModel');
          }

          if (samplersResult.status === 'fulfilled') {
              populateSelect(samplersSelect, JSON.parse(samplersResult.value.responseText), 'name', 'name');
              samplersSelect.value = settings.samplerName;
          } else {
              console.error('获取采样器失败:', samplersResult.reason);
              setError(samplersSelect, 'samplerName');
          }

          if (schedulersResult.status === 'fulfilled') {
              populateSelect(sdSchedulerSelect, JSON.parse(schedulersResult.value.responseText), 'name', 'label');
              sdSchedulerSelect.value = settings.sdScheduler;
          } else {
              console.error('获取调度器失败:', schedulersResult.reason);
              setError(sdSchedulerSelect, 'sdScheduler');
          }

          if (upscalersResult.status === 'fulfilled') {
              populateSelect(upscalersSelect, JSON.parse(upscalersResult.value.responseText), 'name', 'name');
              upscalersSelect.value = settings.hrUpscaler;
          } else {
              console.error('获取放大算法失败:', upscalersResult.reason);
              setError(upscalersSelect, 'hrUpscaler');
          }

          if (lorasResult.status === 'fulfilled') {
              const populateLoras = (select, items) => {
                  if (!select) return;
                  select.innerHTML = '<option value="">-- 选择一个LoRA --</option>';
                  items.forEach(item => select.add(new Option(item.alias, item.name)));
              };
              populateLoras(lorasSelect, JSON.parse(lorasResult.value.responseText));
          } else {
              console.error('获取LoRA列表失败:', lorasResult.reason);
              if(lorasSelect) lorasSelect.innerHTML = '<option value="">刷新失败</option>';
          }

          // --- 处理ControlNet ---
          if (cnModelsResult.status === 'fulfilled' && cnModulesResult.status === 'fulfilled') {
              const cnModels = JSON.parse(cnModelsResult.value.responseText).model_list;
              const cnModules = JSON.parse(cnModulesResult.value.responseText).module_list;
              // 只把获取到的数据存到全局变量里,不做任何UI操作
              window.tempCnData = { models: cnModels, modules: cnModules };
              console.log("ControlNet 数据已刷新并存储。");
          } else {
              console.error('加载 ControlNet 数据失败。可能是插件未安装或API不可用。');
              // 清理暂存数据,同样不做UI操作
              window.tempCnData = { models: [], modules: [] };
          }


          const allSucceeded = results.every(r => r.status === 'fulfilled');
          if (showAlertOnSuccess) {
              if(allSucceeded){
                   alert('SD 数据列表已成功刷新!');
              } else {
                   alert('部分SD数据刷新失败,请打开浏览器控制台(F12)查看详情。');
              }
          }
      }

    async function fetchComfyuiOptions(showAlertOnSuccess = true) {
        const comfyUrl = removeTrailingSlash(document.getElementById('comfyuiUrl').value);
        if (!comfyUrl) {
            if(showAlertOnSuccess) alert('请先输入有效的 ComfyUI URL。');
            return;
        }

        const modelSelect = document.getElementById('comfyuiModel');
        const samplerSelect = document.getElementById('comfyuiSampler');
        const schedulerSelect = document.getElementById('comfyuiScheduler');
        const faceDetailerSamplerSelect = document.getElementById('faceDetailerSampler');
        const faceDetailerModelSelect = document.getElementById('faceDetailerModel');
        const faceDetailerSchedulerSelect = document.getElementById('faceDetailerScheduler');
        const upscaleModelSelect = document.getElementById('comfyuiUpscaleModel');
        const ultimateSamplerSelect = document.getElementById('comfyuiUltimateSampler');
        const ultimateSchedulerSelect = document.getElementById('comfyuiUltimateScheduler');
        const loraSelects = [
            document.getElementById('comfyuiLora'), document.getElementById('comfyuiLora2'),
            document.getElementById('comfyuiLora3'), document.getElementById('comfyuiLora4')
        ];

        const setLoading = (select) => { if(select) select.innerHTML = '<option>加载中...</option>'; };
        setLoading(modelSelect); setLoading(samplerSelect); setLoading(schedulerSelect);
        setLoading(faceDetailerSamplerSelect); setLoading(faceDetailerSchedulerSelect);
        setLoading(faceDetailerModelSelect);
        setLoading(upscaleModelSelect); setLoading(ultimateSamplerSelect); setLoading(ultimateSchedulerSelect);
        loraSelects.forEach(setLoading);

        try {
            const response = await gmXmlHttpRequestPromise({ method: "GET", url: `${comfyUrl}/object_info` });
            const objectInfo = JSON.parse(response.responseText);

            const populateSelect = (select, data, current) => {
                if (!select || !data) return;
                select.innerHTML = '';
                data.forEach(name => select.add(new Option(name, name)));
                select.value = current;
            };

            const ksamplerInfo = objectInfo['KSampler'];
            const ckptInfo = objectInfo['CheckpointLoaderSimple'];
            const loraStackInfo = objectInfo['Lora Loader Stack (rgthree)'];
            const loraLoaderInfo = objectInfo['LoraLoader'];
            const upscaleModelInfo = objectInfo['UpscaleModelLoader'];
            const detectorProviderInfo = objectInfo['UltralyticsDetectorProvider'];

            let loraList = [];
            if(loraStackInfo?.input?.required?.lora_01[0]) {
                 loraList = loraStackInfo.input.required.lora_01[0].filter(lora => lora !== 'None');
            } else if (loraLoaderInfo?.input?.required?.lora_name[0]) {
                 loraList = loraLoaderInfo.input.required.lora_name[0];
            }

            const samplerList = ksamplerInfo.input.required.sampler_name[0];
            const schedulerList = ksamplerInfo.input.required.scheduler[0];
            const upscaleModelList = upscaleModelInfo.input.required.model_name[0];
            const detectorModelList = detectorProviderInfo.input.required.model_name[0];

            populateSelect(samplerSelect, samplerList, settings.comfyuiSampler);
            populateSelect(schedulerSelect, schedulerList, settings.comfyuiScheduler);
            populateSelect(faceDetailerSamplerSelect, samplerList, settings.faceDetailerSampler);
            populateSelect(faceDetailerSchedulerSelect, schedulerList, settings.faceDetailerScheduler);
            populateSelect(upscaleModelSelect, upscaleModelList, settings.comfyuiUpscaleModel);
            populateSelect(ultimateSamplerSelect, samplerList, settings.comfyuiUltimateSampler);
            populateSelect(ultimateSchedulerSelect, schedulerList, settings.comfyuiUltimateScheduler);
            populateSelect(faceDetailerModelSelect, detectorModelList, settings.faceDetailerModel);
            populateSelect(modelSelect, ckptInfo.input.required.ckpt_name[0], settings.comfyuiModel);

            loraSelects.forEach((select, index) => {
                if (!select) return;
                const settingKey = index === 0 ? 'comfyuiLora' : `comfyuiLora${index + 1}`;
                select.innerHTML = '';
                select.add(new Option('None', 'None'));
                loraList.forEach(name => select.add(new Option(name, name)));
                select.value = settings[settingKey] || 'None';
            });

            if (showAlertOnSuccess) {
                alert('ComfyUI 数据列表已成功刷新!');
            }
        } catch (error) {
             console.error('获取ComfyUI选项失败:', error);
             if (showAlertOnSuccess) {
                alert('获取ComfyUI选项列表失败。请检查URL是否正确,以及API是否可访问。');
             }
              const setError = (select) => {
                  if (select) {
                      select.innerHTML = '<option value="">刷新失败</option>';
                      const savedValue = settings[select.id];
                      if(savedValue){
                          select.add(new Option(savedValue, savedValue, true, true));
                      }
                  }
                };
              setError(modelSelect); setError(samplerSelect); setError(schedulerSelect);
              setError(faceDetailerSamplerSelect); setError(faceDetailerSchedulerSelect);
              setError(upscaleModelSelect); setError(ultimateSamplerSelect); setError(ultimateSchedulerSelect);
              setError(faceDetailerModelSelect);
              loraSelects.forEach(setError);
              loraSelects.forEach(select => { if(select) select.innerHTML = '<option value="None">刷新失败</option>'});
        }
    }
    // =========================================================================
    // == 在这里,即 createSettingsPanel 函数之前,插入所有新的辅助函数 ==
    // =========================================================================

    /**
 * 填充所有 ControlNet 单元中的模型和预处理器下拉框。
 */
    function populateControlNetSelects() {
        if (!window.tempCnData) return; // 如果没有获取到数据则不执行

        const { models, modules } = window.tempCnData;
        // VVVV 这是修正后的选择器 VVVV
        // 它会查找在 .controlnet-modal 弹窗内,所有带有 data-index 属性的 .settings-group 元素
        const unitElements = document.querySelectorAll('.controlnet-modal .settings-group[data-index]');

        unitElements.forEach(unitEl => {
            const modelSelect = unitEl.querySelector('.cn-model');
            const moduleSelect = unitEl.querySelector('.cn-module');

            // 在填充前保存当前选中的值
            const currentModel = modelSelect.value;
            const currentModule = moduleSelect.value;

            // 填充模型
            modelSelect.innerHTML = '<option value="None">None</option>';
            if(Array.isArray(models)) {
                models.forEach(name => modelSelect.add(new Option(name, name)));
            }
            // 尝试恢复之前的值
            modelSelect.value = currentModel;


            // 填充预处理器
            moduleSelect.innerHTML = '<option value="none">none</option>';
            if(Array.isArray(modules)) {
                modules.forEach(name => moduleSelect.add(new Option(name, name)));
            }
            // 尝试恢复之前的值
            moduleSelect.value = currentModule;
        });
    }

    /**
 * 根据传入的数据创建一个 ControlNet 单元的 HTML 结构。
 * @param {object} unit - 单个单元的配置数据。
 * @param {number} index - 单元的索引。
 * @returns {string} - HTML 字符串。
 */
    function createControlNetUnitHTML(unit, index) {
        const isEnabled = unit.enabled === true || unit.enabled === 'true';
        const hasImage = unit.image && unit.image.startsWith('data:image');
        // 将整个单元渲染为一个 settings-group 以保持风格统一
        return `
        <div class="settings-group" data-index="${index}">
            <div class="settings-group-title" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                <span><i class="fa-solid fa-puzzle-piece" style="margin-right: 8px; color: var(--accent-primary);"></i>控制单元 ${index + 1}</span>
                <div style="display: flex; align-items: center; gap: 15px;">
                    <label class="switch" title="启用/禁用此单元">
                        <input type="checkbox" class="cn-enabled" ${isEnabled ? 'checked' : ''}>
                        <span class="slider"></span>
                    </label>
                    <button class="panel-button danger remove-cn-unit" title="移除此单元" style="padding: 4px 8px; font-size: 0.8em;"><i class="fa-solid fa-trash-can"></i></button>
                </div>
            </div>

            <div class="unit-body" style="display: flex; gap: 20px; flex-wrap: wrap;">
                <div class="cn-image-area" style="flex: 0 0 160px;">
                    <label>参考图像</label>
                    <div class="cn-image-upload-wrapper">
                        ${hasImage ? `<img src="${unit.image}" class="cn-preview-img">` : `<div class="cn-upload-placeholder"><i class="fa-solid fa-image"></i><span>点击上传</span></div>`}
                        <input type="file" class="cn-image-input" accept="image/*" style="display:none;">
</div>
                </div>
                <div class="cn-settings-area" style="flex-grow: 1; min-width: 300px;">
                    <div class="flex-row">
                        <div><label>模型 (Model): <select class="cn-model"><option value="None">-- 刷新后加载 --</option></select></label></div>
                        <div><label>预处理器 (Preprocessor): <select class="cn-module"><option value="none">-- 刷新后加载 --</option></select></label></div>
                    </div>
                    <div class="flex-row" style="margin-top: 15px;">
                        <div>
                            <label>缩放模式 (Resize Mode):
                                <select class="cn-resize_mode">
                                    <option value="Just Resize">仅调整大小</option>
                                    <option value="Scale to Fit (Inner Fit)" selected>剪裁后缩放</option>
                                    <option value="Envelope (Outer Fit)">缩放后填充空白</option>
                                </select>
                            </label>
                        </div>
                        <div>
                            <label>控制模式 (Control Mode):
                                <select class="cn-control_mode">
                                    <option value="Balanced">平衡 (Balanced)</option>
                                    <option value="My prompt is more important">提示词更重要</option>
                                    <option value="ControlNet is more important">ControlNet更重要</option>
                                </select>
                            </label>
                        </div>
                        <div>
                            <label style="display:flex; align-items:center; gap: 10px; margin-top: 20px;">
                                <label class="switch"><input type="checkbox" class="cn-pixel_perfect"><span class="slider"></span></label>
                                <span>完美像素</span>
                            </label>
                        </div>
                    </div>
                </div>
            </div>
        </div>`;
    }


    function updateControlNetUI() {
        const container = document.getElementById('controlnet-units-container');
        if (!container) return;

        // 解析存储的配置
        let units = [];
        try {
            // 从 settings 对象直接获取,此时它应该是数组了
            const storedUnits = settings.controlNetUnits;
            // 如果 storedUnits 是字符串,则解析;如果是数组,直接使用
            if (typeof storedUnits === 'string') {
                units = JSON.parse(storedUnits || '[]');
            } else if (Array.isArray(storedUnits)) {
                units = storedUnits;
            }
            if (!Array.isArray(units)) { // 最终安全检查
                units = [];
            }
        } catch(e) {
            console.error("解析ControlNet单元配置失败", e);
            units = [];
        }

        container.innerHTML = ''; // 清空现有UI
        units.forEach((unit, index) => {
            container.innerHTML += createControlNetUnitHTML(unit, index);
        });

        // 重新填充下拉框并恢复已存值
        populateControlNetSelects();
        units.forEach((unit, index) => {
            const unitEl = container.querySelector(`.controlnet-unit[data-index="${index}"]`);
            if (unitEl) {
                if (unit.model) unitEl.querySelector('.cn-model').value = unit.model;
                if (unit.module) unitEl.querySelector('.cn-module').value = unit.module;
                if (unit.resize_mode) unitEl.querySelector('.cn-resize_mode').value = unit.resize_mode;
                if (unit.control_mode) unitEl.querySelector('.cn-control_mode').value = unit.control_mode;
                const ppCheckbox = unitEl.querySelector('.cn-pixel_perfect');
                ppCheckbox.checked = (unit.pixel_perfect === true || unit.pixel_perfect === 'true');
            }
        });

        // 检查是否达到上限
        const addButton = document.getElementById('add-controlnet-unit');
        if (addButton) {
            addButton.style.display = units.length >= 4 ? 'none' : 'block';
        }
    }



  function workflow_save(){
    stylInput("请输入工作流名称").then((name) => {
        if (!name) return;
        const editor = document.getElementById("comfyuiWorkflowEditor");
        const select = document.getElementById("comfyuiCurrentWorkflow");
        try {
            // 尝试解析以确保是有效的JSON
            const parsed = JSON.parse(editor.value);
            // 格式化后保存
            const formattedJson = JSON.stringify(parsed, null, 2);
            editor.value = formattedJson;

            settings.comfyuiWorkflows[name] = formattedJson;
            settings.comfyuiCurrentWorkflow = name;

            // 更新下拉列表
            if (!Array.from(select.options).some(opt => opt.value === name)) {
                select.add(new Option(name, name));
            }
            select.value = name;

            GM_setValue("comfyuiWorkflows", JSON.stringify(settings.comfyuiWorkflows));
            GM_setValue("comfyuiCurrentWorkflow", name);
            alert(`工作流 "${name}" 已保存!`);
        } catch (e) {
            alert("保存失败!编辑器内容不是有效的JSON格式。");
            console.error("JSON parse error:", e);
        }
      });
  }

  function workflow_delete(){
    stylishConfirm("您确定要删除这个工作流吗?").then((confirmed) => {
        if (confirmed) {
            const select = document.getElementById("comfyuiCurrentWorkflow");
            const nameToDelete = select.value;
            if (Object.keys(settings.comfyuiWorkflows).length <= 1) {
                alert("无法删除最后一个工作流!");
                return;
            }
            delete settings.comfyuiWorkflows[nameToDelete];
            select.remove(select.selectedIndex);
            const newCurrent = select.options[0].value;
            select.value = newCurrent;
            settings.comfyuiCurrentWorkflow = newCurrent;
            workflow_change(); // 更新编辑器内容
            GM_setValue("comfyuiWorkflows", JSON.stringify(settings.comfyuiWorkflows));
            GM_setValue("comfyuiCurrentWorkflow", newCurrent);
            alert(`工作流 "${nameToDelete}" 已删除。`);
        }
      });
  }

  function workflow_change(){
    const select = document.getElementById("comfyuiCurrentWorkflow");
    const editor = document.getElementById("comfyuiWorkflowEditor");
    const workflowName = select.value;
    settings.comfyuiCurrentWorkflow = workflowName;
    editor.value = settings.comfyuiWorkflows[workflowName] || "{}";
  }


  function tishici_save(){
    stylInput("请输入配置名称").then((result) => {
        if (result) {
          const negativePrompt = document.getElementById("negativePrompt").value;
          const positivePrompt = document.getElementById("positivePrompt").value;
          const selectElement = document.getElementById("yusheid");
          if(!settings.yushe.hasOwnProperty(result)){
            let newOption = new Option(result, result);
            selectElement.add(newOption);
          }
          selectElement.value=result;
          settings.yusheid=result;
          settings.yushe[result]={"positivePrompt":positivePrompt,"negativePrompt":negativePrompt};
          GM_setValue("yushe",JSON.stringify(settings.yushe));
          GM_setValue("yusheid",settings.yusheid);
        }
      });
  }

  function tishici_delete(){
    stylishConfirm("是否确定删除").then((result) => {
        if (result) {
            const selectElement = document.getElementById("yusheid");
            if (selectElement.value === "默认") return alert("默认配置不能删除!");
            Reflect.deleteProperty(settings.yushe, selectElement.value);
            selectElement.remove(selectElement.selectedIndex);
            selectElement.value="默认";
            settings.yusheid="默认";
            tishici_change();
            GM_setValue("yusheid",settings.yusheid);
            GM_setValue("yushe",JSON.stringify(settings.yushe));
        }
      });
  }

  function tishici_change(){
    const negativePrompt = document.getElementById("negativePrompt");
    const positivePrompt = document.getElementById("positivePrompt");
    const selectElement = document.getElementById("yusheid");
    settings.yusheid=selectElement.value;
    const currentYushe = settings.yushe[settings.yusheid] || {"positivePrompt": '', "negativePrompt": ''};
    negativePrompt.value=currentYushe.negativePrompt;
    positivePrompt.value=currentYushe.positivePrompt;
  }
  function size_change(){
    const widthInput = document.getElementById("width");
    const heightInput = document.getElementById("height");
    const selectElement = document.getElementById("size");
    const selectedValue = selectElement.value;

    let newWidth = widthInput.value;
    let newHeight = heightInput.value;

    if (selectedValue === 'Custom') {
    } else if (selectedValue.includes('x')) {
        const [w, h] = selectedValue.split('x');
        widthInput.value = w;
        heightInput.value = h;
    } else {
        switch (selectedValue) {
            case '竖图':
                widthInput.value = '832';
                heightInput.value = '1216';
                break;
            case '横图':
                widthInput.value = '1216';
                heightInput.value = '832';
                break;
            case '方图':
                widthInput.value = '1024';
                heightInput.value = '1024';
                break;
        }
    }

    // 实时更新内存中的 settings 对象
    settings.size = selectedValue;
    settings.width = widthInput.value;
    settings.height = heightInput.value;

    console.log('实时更新分辨率设置为:', {size: settings.size, width: settings.width, height: settings.height});
}
  function showToast(message, type = 'success', duration = 2500) {
      const panel = document.getElementById('settings-panel');
      if (!panel) return; // 如果面板不存在,则不执行任何操作

      const toast = document.createElement('div');
      toast.className = `toast-notification ${type}`; // 应用基础和特定类型的样式
      toast.textContent = message;

      panel.appendChild(toast);

      // 短暂延迟后添加入场动画,确保过渡效果生效
      setTimeout(() => {
          toast.classList.add('visible');
      }, 20);

      // 在指定时间后,添加离场动画
      setTimeout(() => {
          toast.classList.remove('visible');
      }, duration);

      // 在动画结束后,从 DOM 中移除元素,避免 clutter
      setTimeout(() => {
          if (toast.parentNode) {
              toast.parentNode.removeChild(toast);
          }
      }, duration + 500); // 持续时间 + CSS过渡时间
  }
  function stylishConfirm(message) {
    return new Promise((resolve) => {
      const overlay = document.createElement('div');
      overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 10001;';
      const confirmBox = document.createElement('div');
      confirmBox.style.cssText = 'position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%); background-color: #1e1e2e; color: #c0caf5; padding: 25px; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.4); z-index: 10002; text-align: center; border: 1px solid #414868;';
      confirmBox.innerHTML = `<p style="margin: 0 0 20px; font-size: 1.1em; white-space: pre-wrap;">${message}</p>
        <div style="display: flex; gap: 10px; justify-content: center;">
            <button id="cancelBtn" class="panel-button">取消</button>
            <button id="confirmBtn" class="panel-button primary">确定</button>
        </div>`;
      document.body.appendChild(overlay);
      document.body.appendChild(confirmBox);
      const close = (value) => { document.body.removeChild(overlay); document.body.removeChild(confirmBox); resolve(value); };
      confirmBox.querySelector('#cancelBtn').addEventListener('click', () => close(false));
      confirmBox.querySelector('#confirmBtn').addEventListener('click', () => close(true));
    });
  }
  function stylInput(message) {
    return new Promise((resolve) => {
      const overlay = document.createElement('div');
      overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 10001;';
      const confirmBox = document.createElement('div');
      confirmBox.style.cssText = 'position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%); background-color: #1e1e2e; color: #c0caf5; padding: 25px; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.4); z-index: 10002; border: 1px solid #414868; min-width:300px;';
      confirmBox.innerHTML = `<p style="margin: 0 0 15px;">${message}</p>`;
      const input = document.createElement('input');
      input.type = 'text';
      input.style.cssText = 'width: 100%; background-color: #2a2e3e; color: #c0caf5; border: 1px solid #414868; border-radius: 5px; padding: 8px 10px; box-sizing: border-box;';
      input.onkeydown = (e) => { if (e.key === 'Enter') confirmBtn.click(); };
      confirmBox.appendChild(input);
      const btnContainer = document.createElement('div');
      btnContainer.style.cssText = 'display:flex; justify-content: flex-end; gap: 10px; margin-top: 20px;';
      const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.className = 'panel-button';
      const confirmBtn = document.createElement('button'); confirmBtn.textContent = '确定'; confirmBtn.className = 'panel-button primary';
      btnContainer.append(cancelBtn, confirmBtn);
      confirmBox.appendChild(btnContainer);
      document.body.append(overlay, confirmBox);
      const close = (value) => { document.body.removeChild(overlay); document.body.removeChild(confirmBox); resolve(value); };
      cancelBtn.addEventListener('click', () => close(false));
      confirmBtn.addEventListener('click', () => close(input.value));
      input.focus();
    });
  }

   async function clearCache() {
        if (await stylishConfirm("您确定要清空所有图片缓存吗?此操作不可逆。")) {
            try {
                // 1. 清空主图片存储区
                await StoreClear();

                // 2. 重置并保存空的映射表
                locationToImageIdMap = {};
                await Storereadwrite({ id: 'locationMap', data: {} });

                // 3. 重置并保存全局ID计数器
                globalImageCounter = 0;
                await GM_setValue('globalImageCounter', 0);

                // 4. 清理UI
                initCacheViewer(); // 这会更新UI显示为空

                alert("已清除所有图片缓存。");

            } catch(error) {
                console.error("清空缓存失败:", error);
                alert("清空缓存失败,请查看控制台。");
            }
        }
    }

    function removeTrailingSlash(str) {
      if (typeof str !== 'string') return '';
      return str.endsWith('/') ? str.slice(0, -1) : str;
    }

    async function resetCurrentModeSettings() {
        const currentMode = settings.currentMode;
        const modeName = {sd: "Stable Diffusion", nai: "NovelAI", comfyui: "ComfyUI"}[currentMode];

        if (!(await stylishConfirm(`您确定要重置 ${modeName} 模式的专属设置吗?\n(例如API地址、模型等,不包括通用尺寸、提示词预设等)\n此操作无法撤销。`))) {
            return;
        }

        const keysToResetByMode = {
            sd: [
                'sdUrl', 'samplerName', 'sdScheduler', 'sdModel', 'enableHr', 'hrScale',
                'controlNetEnabled',
                'controlNetUnits',
                'hrDenoisingStrength', 'hrUpscaler', 'hrSecondPassSteps', 'adetailerEnabled',
                'adModel', 'adDenoisingStrength', 'adMaskBlur', 'adInpaintPadding', 'sdCfgScale', 'seed', 'restoreFaces'
            ],
            nai: [
                'naiApiUrl', 'naiToken', 'naiModel', 'naiSampler', 'naiScale', 'naiCfg',
                'naiNoiseSchedule', 'naiVibeTransferEnabled', 'naiVibeTransferImages'
            ],
            comfyui: [
                'comfyuiUrl', 'comfyuiWorkflows', 'comfyuiCurrentWorkflow', 'comfyuiModel',
                'comfyuiSampler', 'comfyuiScheduler', 'comfyuiLora', 'comfyuiLoraStrength',
                'comfyuiLora2', 'comfyuiLoraStrength2', 'comfyuiLora3', 'comfyuiLoraStrength3',
                'comfyuiLora4', 'comfyuiLoraStrength4', 'faceDetailerGuideSize', 'faceDetailerMaxSize','comfyuiUseCustomWorkflowOnly',
                'faceDetailerSteps', 'sdCfgScale', 'faceDetailerCfg', 'faceDetailerSampler', 'faceDetailerScheduler','faceDetailerModel',
                'comfyuiUpscaleModel', 'comfyuiUltimateUpscaleBy', 'comfyuiUltimateSteps',
                'comfyuiUltimateCfg', 'comfyuiUltimateSampler', 'comfyuiUltimateScheduler',
                'comfyuiUltimateDenoise', 'comfyuiUltimateModeType'
            ]
        };

        const keysToReset = keysToResetByMode[currentMode];
        if (!keysToReset) {
            alert("错误:未知的当前模式。");
            return;
        }

        for (const key of keysToReset) {
            if (defaultSettings.hasOwnProperty(key)) {
                const defaultValue = defaultSettings[key];
                settings[key] = JSON.parse(JSON.stringify(defaultValue));

                if (typeof defaultValue === 'object' && defaultValue !== null) {
                    GM_setValue(key, JSON.stringify(defaultValue));
                } else {
                    GM_setValue(key, defaultValue);
                }
                console.log(`[Reset] Setting '${key}' restored to default.`);
            }
        }

        alert(`${modeName} 模式的设置已重置为默认值。\n请关闭并重新打开设置面板以查看更改。`);
        hideSettingsPanel();
    }


    function saveSettings() {
        if (settings.currentMode === 'nai') {
            const vibeContainer = document.getElementById('naiVibeImageListContainer');
            const vibeImagesData = [];
            if (vibeContainer) {
                const items = vibeContainer.querySelectorAll('.vibe-item');
                items.forEach(item => {
                    const type = item.dataset.type;
                    const infoExtract = item.querySelector('.info-extract-slider').value;
                    const refStrength = item.querySelector('.ref-strength-slider').value;

                    if (type === 'vibeFile' && item.dataset.vibeData) {
                        const vibeData = JSON.parse(item.dataset.vibeData);
                        // 更新导入信息以反映滑块的当前值
                        vibeData.importInfo.information_extracted = infoExtract;
                        vibeData.importInfo.strength = refStrength;
                        vibeImagesData.push({
                            type: 'vibeFile',
                            vibeData: vibeData,
                            // 也保存滑块值,方便读取
                            infoExtract: infoExtract,
                            refStrength: refStrength
                        });
                    } else if (type === 'image' && item.dataset.base64) {
                        vibeImagesData.push({
                            type: 'image',
                            base64: item.dataset.base64,
                            infoExtract: infoExtract,
                            refStrength: refStrength
                        });
                    }
                });
            }
            settings.naiVibeTransferImages = vibeImagesData;
            GM_setValue('naiVibeTransferImages', JSON.stringify(vibeImagesData));
        }

        // --- 保存统一的提示词 ---
        const positivePromptValue = document.getElementById('positivePrompt').value;
        if (settings.currentMode === 'sd' || settings.currentMode === 'comfyui') {
            settings.fixedPrompt = positivePromptValue;
            GM_setValue('fixedPrompt', positivePromptValue);
        } else { // nai
            settings.naiPositivePrompt = positivePromptValue;
            GM_setValue('naiPositivePrompt', positivePromptValue);
        }
        settings.negativePrompt = document.getElementById('negativePrompt').value;
        settings.steps = document.getElementById('steps').value;
        GM_setValue('negativePrompt', settings.negativePrompt);
        GM_setValue('steps', settings.steps);

        // **重要**:保存ComfyUI工作流编辑器中的内容到settings对象
        if (settings.currentMode === 'comfyui') {
            const editor = document.getElementById('comfyuiWorkflowEditor');
            const workflowName = settings.comfyuiCurrentWorkflow;
            if(editor && workflowName) {
                try {
                    const parsed = JSON.parse(editor.value);
                    settings.comfyuiWorkflows[workflowName] = JSON.stringify(parsed, null, 2);
                } catch(e) {
                    console.error("无法保存工作流,不是有效的JSON:", e);
                    alert("工作流编辑器中的内容不是有效的JSON,该工作流的更改将不会被保存。");
                }
            }
        }


        const settingsToSkip = new Set([
            'naiVibeTransferImages', 'fixedPrompt', 'naiPositivePrompt', 'negativePrompt', 'steps'
        ]);

        for (const key of Object.keys(defaultSettings)) {
            if (settingsToSkip.has(key)) continue;

             // 对于对象类型(如yushe, comfyuiWorkflows),直接从内存中的settings对象取值并保存
            if (typeof defaultSettings[key] === 'object' && defaultSettings[key] !== null) {
                GM_setValue(key, JSON.stringify(settings[key]));
                continue;
            }

            const element = document.getElementById(key);
            if (element) {
                let valueToSave;
                if (element.type === 'checkbox') {
                    valueToSave = element.checked ? 'true' : 'false';
                } else {
                    valueToSave = element.value;
                }
                settings[key] = valueToSave;
                if(key=="sdUrl" || key == "naiApiUrl" || key == "comfyuiUrl") settings[key] = removeTrailingSlash(settings[key]);
                if((key=="startTag"||key=="endTag") && element.value !== GM_getValue(key, '')) sendGenerateTagsResponse();
                GM_setValue(key, settings[key]);
            }
        }
        console.log('设置已保存');
        hideSettingsPanel();
    }
    // 【新增功能】在您的脚本中添加这个新函数
    /**
    * 使用已保存的单个值来填充下拉选择框。
    * 这避免了在面板加载时需要进行网络请求。
    * @param {string} selectId - <select> 元素的ID。
    * @param {string} savedValue - 从 settings 中读取的已保存的值。
    * @param {string} [placeholder='-- 点击刷新加载 --'] - 如果没有保存值时显示的文本。
    * @param {string} [placeholderValue=''] - 如果没有保存值时选项的value。
    */
    function populateSelectWithSavedValue(selectId, savedValue, placeholder = '-- 点击刷新加载 --', placeholderValue = '') {
        const select = document.getElementById(selectId);
        if (!select) return;

        select.innerHTML = ''; // 清空所有现有选项

        if (savedValue && savedValue !== '' && savedValue !== 'None') {
            // 如果有一个有效值被保存了,就创建并选中这个选项
            const option = new Option(savedValue, savedValue, true, true); // text, value, defaultSelected, selected
            select.add(option);
        } else {
            // 否则,显示占位符文本
            const option = new Option(placeholder, placeholderValue);
            select.add(option);
        }
    }


    function hideSettingsPanel() {
        let panel = document.getElementById('settings-panel');
        if(panel) panel.classList.remove('visible');
    }
    function closeSettings() { hideSettingsPanel(); }

    function showSettingsPanel() {
        let panel = document.getElementById('settings-panel');
        if (panel) panel.remove();
        panel = createSettingsPanel();

        const settingsToShow = new Set(Object.keys(settings));
        settingsToShow.delete('naiVibeTransferImages');
        settingsToShow.delete('yushe');
        settingsToShow.delete('comfyuiWorkflows');
        settingsToShow.delete('fixedPrompt');
        settingsToShow.delete('naiPositivePrompt');


        for(const key of settingsToShow) {
            const el = panel.querySelector(`#${key}`);
            if (el) {
                if (el.type === 'checkbox') el.checked = (settings[key] === 'true' || settings[key] === true);
                else el.value = settings[key];
            }
        }
        // 智能处理分辨率下拉框
        const sizeDropdown = panel.querySelector('#size');
        const widthInput = panel.querySelector('#width');
        const heightInput = panel.querySelector('#height');

        if (sizeDropdown && widthInput && heightInput) {
            // 步骤1: 总是先根据 settings 恢复准确的宽高数值
            widthInput.value = settings.width;
            heightInput.value = settings.height;

            // 步骤2: 智能判断并设置下拉框的显示
            let determinedSize = "Custom"; // 默认为自定义
            const currentSizeValue = `${settings.width}x${settings.height}`;

            if (settings.currentMode === 'nai') {
                if (currentSizeValue === '832x1216') determinedSize = '竖图';
                else if (currentSizeValue === '1216x832') determinedSize = '横图';
                else if (currentSizeValue === '1024x1024') determinedSize = '方图';
            }

            // 如果不是NAI模式的特殊值,或者当前不是NAI模式,则尝试匹配通用值
            if (determinedSize === 'Custom') {
                 const generalOption = Array.from(sizeDropdown.options).find(opt => opt.value === currentSizeValue);
                 if (generalOption) {
                     determinedSize = generalOption.value;
                 }
            }

            // 如果settings中保存的值(比如竖图)本身就存在于选项中,则优先使用它
            if (Array.from(sizeDropdown.options).some(opt => opt.value === settings.size)) {
                sizeDropdown.value = settings.size;
            } else {
                sizeDropdown.value = determinedSize;
            }

            // 步骤3: (可选但推荐) 为 width 和 height 输入框添加监听,如果手动修改,则自动将下拉框设为 "Custom"
            const syncToCustom = () => {
                sizeDropdown.value = "Custom";
                settings.size = "Custom";
                settings.width = widthInput.value;
                settings.height = heightInput.value;
            };
            widthInput.removeEventListener('input', syncToCustom); // 防止重复绑定
            heightInput.removeEventListener('input', syncToCustom); // 防止重复绑定
            widthInput.addEventListener('input', syncToCustom);
            heightInput.addEventListener('input', syncToCustom);
        }
        const statusLabel = panel.querySelector('#script-status-label');
        if(statusLabel) {
            const isEnabled = settings.scriptEnabled === true || settings.scriptEnabled === 'true';
            statusLabel.textContent = isEnabled ? '脚本已启用' : '脚本已禁用';
            statusLabel.style.color = isEnabled ? 'var(--accent-green)' : 'var(--accent-secondary)';
        }
        panel.classList.add('visible');
        // -- 初始化缓存查看器 --
        const refreshBtn = panel.querySelector('#refresh-cache-view');
        if (refreshBtn) {
            refreshBtn.addEventListener('click', initCacheViewer);
        }
        const downloadPageBtn = panel.querySelector('#download-cache-page');
        if (downloadPageBtn) {
            downloadPageBtn.addEventListener('click', async () => {
                const totalItems = sortedCache.length;
                if (totalItems === 0) {
                    showToast('缓存为空,无法下载。', 'error');
                    return;
                }
                const startIndex = (currentCachePage - 1) * ITEMS_PER_PAGE;
                const endIndex = Math.min(startIndex + ITEMS_PER_PAGE, totalItems);
                const pageItems = sortedCache.slice(startIndex, endIndex);

                await downloadImagesAsZip(pageItems, `sillytavern_cache_page_${currentCachePage}.zip`);
            });
        }

        const downloadAllBtn = panel.querySelector('#download-cache-all');
        if (downloadAllBtn) {
            downloadAllBtn.addEventListener('click', async () => {
                if (sortedCache.length === 0) {
                    showToast('缓存为空,无法下载。', 'error');
                    return;
                }
                // 直接使用完整的 sortedCache 数组下载所有图片
                await downloadImagesAsZip(sortedCache, `sillytavern_cache_all.zip`);
            });
        }
        // 打开面板时自动加载第一页
        initCacheViewer();

        if(settings.currentMode === 'sd') {
            // 【修改点】不再自动获取数据,而是用已保存的值填充下拉框
            populateSelectWithSavedValue('sdModel', settings.sdModel);
            populateSelectWithSavedValue('samplerName', settings.samplerName);
            populateSelectWithSavedValue('sdScheduler', settings.sdScheduler);
            populateSelectWithSavedValue('hrUpscaler', settings.hrUpscaler);
            // LoRA 选择框初始为空,提示用户刷新
            populateSelectWithSavedValue('sdLora', '', '-- 点击刷新加载 --');
            const openCnBtn = panel.querySelector('#open-controlnet-modal-btn');
            if (openCnBtn) {
                openCnBtn.addEventListener('click', openControlNetModal);
            }
        } else if (settings.currentMode === 'comfyui') {
            populateSelectWithSavedValue('comfyuiModel', settings.comfyuiModel);
            populateSelectWithSavedValue('comfyuiSampler', settings.comfyuiSampler);
            populateSelectWithSavedValue('comfyuiScheduler', settings.comfyuiScheduler);
            populateSelectWithSavedValue('faceDetailerSampler', settings.faceDetailerSampler);
            populateSelectWithSavedValue('faceDetailerScheduler', settings.faceDetailerScheduler);
            populateSelectWithSavedValue('faceDetailerModel', settings.faceDetailerModel);
            populateSelectWithSavedValue('comfyuiUpscaleModel', settings.comfyuiUpscaleModel);
            populateSelectWithSavedValue('comfyuiUltimateSampler', settings.comfyuiUltimateSampler);
            populateSelectWithSavedValue('comfyuiUltimateScheduler', settings.comfyuiUltimateScheduler);
            populateSelectWithSavedValue('comfyuiLora', settings.comfyuiLora, 'None', 'None');
            populateSelectWithSavedValue('comfyuiLora2', settings.comfyuiLora2, 'None', 'None');
            populateSelectWithSavedValue('comfyuiLora3', settings.comfyuiLora3, 'None', 'None');
            populateSelectWithSavedValue('comfyuiLora4', settings.comfyuiLora4, 'None', 'None');
        } else {
            const naiChannelSelect = document.getElementById('naiChannel');
            const naiApiUrlInput = document.getElementById('naiApiUrl');

            if (naiChannelSelect && naiApiUrlInput) {
                // 保存用户手动修改的 URL
                let userModifiedUrls = GM_getValue('naiUserModifiedUrls', {});

                // 切换渠道时自动填充默认值
                const updateUrlByChannel = () => {
                    const channel = naiChannelSelect.value;
                    const officialDefault = naiApiUrlInput.dataset.defaultOfficial;
                    const proxyDefault = naiApiUrlInput.dataset.defaultProxy;

                    const savedUrl = userModifiedUrls[channel];
                    if (savedUrl) {
                        naiApiUrlInput.value = savedUrl;
                    } else {
                        naiApiUrlInput.value = channel === 'official' ? officialDefault : proxyDefault;
                    }
                };

                // 监听渠道切换
                naiChannelSelect.addEventListener('change', updateUrlByChannel);

                // 监听用户手动修改保存
                naiApiUrlInput.addEventListener('change', () => {
                    const channel = naiChannelSelect.value;
                    userModifiedUrls[channel] = naiApiUrlInput.value;
                    GM_setValue('naiUserModifiedUrls', userModifiedUrls);
                });

                // 初始化时触发一次
                updateUrlByChannel();
            }
            if (settings.naiVibeTransferImages && Array.isArray(settings.naiVibeTransferImages)) {
                // 根据保存的类型来重新创建缩略图
                settings.naiVibeTransferImages.forEach(imgData => {
                    // 旧数据兼容:如果没有 type,则认为是 image
                    if (!imgData.type || imgData.type === 'image') {
                        createVibeThumbnail({
                            type: 'image',
                            base64: imgData.base64,
                            infoExtract: imgData.infoExtract,
                            refStrength: imgData.refStrength
                        });
                    } else if (imgData.type === 'vibeFile') {
                         createVibeThumbnail(imgData); // vibeFile 数据结构是完整的,直接传递
                    }
                });
            }
        }
    }


    function hideSettingsPanel() {
        let panel = document.getElementById('settings-panel');
        if(panel) panel.classList.remove('visible');
    }
    function unzipFile(arrayBuffer) {
        return new Promise((resolve, reject) => {
            JSZip.loadAsync(arrayBuffer)
                .then(function(zip) {
                    // 遍历 ZIP 文件中的所有文件
                    const imageFile = Object.values(zip.files).find(f => !f.dir && (f.name.endsWith('.png') || f.name.endsWith('.jpg')));
                    if (imageFile) {
                         imageFile.async('base64').then(resolve).catch(reject);
                    } else {
                         reject(new Error("ZIP文件中未找到图片。"));
                    }
                }).catch(reject);
        });
    }
    function isElementHidden(id) { const el = document.getElementById(id); return !el || window.getComputedStyle(el).display === 'none'; }
    function checkSendBuClass() { return isElementHidden('send_but') || !isElementHidden('mes_stop'); }
    function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
    function parsePromptStringWithCoordinates(promptString) {
        const result = {
            'Scene Composition': '', 'Character 1 Prompt': '', 'Character 1 UC': '',
            'Character 2 Prompt': '', 'Character 2 UC': '', 'Character 3 Prompt': '', 'Character 3 UC': '',
            'Character 4 Prompt': '', 'Character 4 UC': '', 'Character 1 centers': '',
            'Character 2 centers': '', 'Character 3 centers': '', 'Character 4 centers': '',
            'Character 1 coordinates': {}, 'Character 2 coordinates': {}, 'Character 3 coordinates': {}, 'Character 4 coordinates': {}
        };
        const sceneMatch = promptString.match(/Scene Composition:([^;]+);/);
        if (sceneMatch) result['Scene Composition'] = sceneMatch[1].trim();
        for (let i = 1; i <= 4; i++) {
            const promptMatch = promptString.match(new RegExp(`Character ${i} Prompt:([^;|]+)(?:\\|centers:([^;]+))?;`));
            if (promptMatch) {
                result[`Character ${i} Prompt`] = promptMatch[1].trim();
                if (promptMatch[2]) {
                    result[`Character ${i} centers`] = promptMatch[2].trim();
                    result[`Character ${i} coordinates`] = centersToCoordinates(promptMatch[2].trim());
                } else {
                    result[`Character ${i} coordinates`] = {};
                }
            }
            const ucMatch = promptString.match(new RegExp(`Character ${i} UC:([^;]+);`));
            if (ucMatch) result[`Character ${i} UC`] = ucMatch[1].trim();
        }
        return result;
    }

    function centersToCoordinates(centers) {
        if (!centers) return {};
        const match = centers.match(/([a-e])([1-5])/i);
        if (!match) return {};
        const column = match[1].toLowerCase();
        const row = parseInt(match[2]);
        const columnMap = { 'a': 0.1, 'b': 0.3, 'c': 0.5, 'd': 0.7, 'e': 0.9 };
        const rowMap = { 1: 0.1, 2: 0.3, 3: 0.5, 4: 0.7, 5: 0.9 };
        return { x: columnMap[column] || 0.5, y: rowMap[row] || 0.5 };
    }

  async function sd(button1,Xwidth=null,Xheight=null){
      if(!button1.id.includes("image")) return;
      button1.textContent="加载中";
      const locationHash = button1.dataset.locationHash; // 获取位置哈希
      const url = removeTrailingSlash(settings.sdUrl);
    // =================================================================
    // =================================================================
    //   try {
    //       await gmXmlHttpRequestPromise({
    //           method: "POST", url: `${url}/sdapi/v1/options`,
    //           headers: { "Content-Type": "application/json" }, data: JSON.stringify({ "sd_model_checkpoint": settings.sdModel })
    //       });
    //   } catch (e) {
    //       button1.textContent="生成图片";
    //       alert(`切换SD模型失败! 请检查模型名称 "${settings.sdModel}"。`);
    //       console.error("模型切换失败:", e); return;
    //   }
    // =================================================================
    // =================================================================
      let prompt = await zhengmian(settings.fixedPrompt,button1.dataset.link,settings.AQT);
      let negative_prompt = await fumian(settings.negativePrompt,settings.UCP);
      const payload = {
          prompt, negative_prompt,
          steps: Number(settings.steps),
          sampler_name: settings.samplerName,
          scheduler: settings.sdScheduler, // 应用选择的调度器
          width: Xwidth ? Number(Xwidth) : Number(settings.width),
          height: Xheight ? Number(Xheight) : Number(settings.height),
          restore_faces: settings.restoreFaces === 'true',
          cfg_scale: Number(settings.sdCfgScale),
          seed: settings.seed === 0 || settings.seed === "" ? -1 : Number(settings.seed)
      };

      // Hires. fix 参数处理
      if (settings.enableHr === 'true') {
          Object.assign(payload, {
              enable_hr: true,
              hr_scale: parseFloat(settings.hrScale),
              denoising_strength: parseFloat(settings.hrDenoisingStrength),
              hr_upscaler: settings.hrUpscaler,
              hr_second_pass_steps: parseInt(settings.hrSecondPassSteps, 10)
          });
      }

      // 初始化 alwayson_scripts,用于合并 ADetailer 和 ControlNet
      payload.alwayson_scripts = {};

      // ADetailer 参数处理
      if (settings.adetailerEnabled === 'true') {
          payload.alwayson_scripts["ADetailer"] = {
              "args": [
                  true,
                  false,
                  {
                      ad_model: settings.adModel,
                      ad_denoising_strength: parseFloat(settings.adDenoisingStrength),
                      ad_mask_blur: parseInt(settings.adMaskBlur, 10),
                      ad_inpaint_only_masked_padding: parseInt(settings.adInpaintPadding, 10)
                  }
              ]
          };
      }

      // ControlNet 参数处理
      if (settings.controlNetEnabled === 'true') {
          try {
              const cn_units = settings.controlNetUnits || [];
              const active_units = cn_units.filter(unit => unit.enabled && unit.image);

              if (active_units.length > 0) {
                  payload.alwayson_scripts["controlnet"] = {
                      "args": active_units.map(unit => ({
                          "enabled": unit.enabled,
                          "image": unit.image,
                          "module": unit.module,
                          "model": unit.model,
                          "weight": unit.weight || 1.0,
                          "resize_mode": unit.resize_mode,
                          "lowvram": unit.lowvram || false,
                          "processor_res": parseInt(unit.processor_res || 512, 10),
                          "threshold_a": parseInt(unit.threshold_a || 64, 10),
                          "threshold_b": parseInt(unit.threshold_b || 64, 10),
                          "guidance_start": parseFloat(unit.guidance_start || 0.0),
                          "guidance_end": parseFloat(unit.guidance_end || 1.0),
                          "control_mode": unit.control_mode,
                          "pixel_perfect": unit.pixel_perfect || false
                      }))
                  };
              }
          } catch(e) {
              console.error("解析或应用ControlNet配置时出错:", e);
          }
      }

      // 如果没有任何 alwayson_scripts 启用,则从 payload 中删除该键,以保持请求整洁
      if (Object.keys(payload.alwayson_scripts).length === 0) {
          delete payload.alwayson_scripts;
      }

      console.log("SD 生成参数:", payload);


      if(!xiancheng) await sleep(1000);
      xiancheng=false;
      try {
          const newImageId = await getNextImageId(); // 获取新的唯一ID
          const response = await gmXmlHttpRequestPromise({
              method: "POST", url: `${url}/sdapi/v1/txt2img`, data: JSON.stringify(payload),
              headers: { "Content-Type": "application/json" },
              timeout: 300000        // ← 放在这里
          });
          const r = JSON.parse(response.responseText);
          const dataURL = "data:image/png;base64," + r.images[0];
          await setItemImg(newImageId, dataURL, locationHash); // 使用新ID和位置哈希保存图片;

          if(Xwidth && Xheight) return dataURL;

          let imgSpan = button1.ownerDocument.getElementById(button1.name);
          const img = document.createElement('img');
          img.src = dataURL;
          img.alt = "Generated Image"; img.dataset.name = imgSpan.dataset.name;
          // 【核心修改】当处于兼容模式时,应用自适应样式
          if (settings.displayMode === '兼容前端') {
              img.style.maxWidth = '100%';
              img.style.height = 'auto';
              img.style.minWidth = '50px';
              img.style.minHeight = '50px';
              img.style.objectFit = 'contain';
              img.style.display = 'block';
              img.style.borderRadius = "5px";
          }
          if(settings.dbclike=="true"){
              imgSpan.style.textAlign = 'center';
              button1.style.cssText = 'width: 0; height: 0; overflow: hidden; padding: 0; border: none;';
          }
          imgSpan.replaceChildren(img);
      } catch (error) {
          alert("图片生成请求失败. 错误: " + (error.responseText || error.statusText || '未知网络错误'));
      } finally {
          xiancheng=true;
          if (button1.isConnected) {
              button1.textContent="生成图片";
              }
      }
  }
    function parsePromptStringWithCoordinates(promptString) {
        const result = {
            'Scene Composition': '',
            'Character 1 Prompt': '',
            'Character 1 UC': '',
            'Character 2 Prompt': '',
            'Character 2 UC': '',
            'Character 3 Prompt': '',
            'Character 3 UC': '',
            'Character 4 Prompt': '',
            'Character 4 UC': '',
            'Character 1 centers': '',
            'Character 2 centers': '',
            'Character 3 centers': '',
            'Character 4 centers': '',
            'Character 1 coordinates': {},
            'Character 2 coordinates': {},
            'Character 3 coordinates': {},
            'Character 4 coordinates': {}
        };
        const sceneMatch = promptString.match(/Scene Composition:([^;]+);/);
        if (sceneMatch) { result['Scene Composition'] = sceneMatch[1].trim(); }
        for (let i = 1; i <= 4; i++) {
            const promptMatch = promptString.match(new RegExp(`Character ${i} Prompt:([^;|]+)(?:\\|centers:([^;]+))?;`));
            if (promptMatch) {
                result[`Character ${i} Prompt`] = promptMatch[1].trim();
                if (promptMatch[2]) {
                    result[`Character ${i} centers`] = promptMatch[2].trim();
                    result[`Character ${i} coordinates`] = centersToCoordinates(promptMatch[2].trim());
                } else {
                    result[`Character ${i} coordinates`] = {}
                }
            }
            const ucMatch = promptString.match(new RegExp(`Character ${i} UC:([^;]+);`));
            if (ucMatch) { result[`Character ${i} UC`] = ucMatch[1].trim(); }
        }
        return result;
    }

    function centersToCoordinates(centers) {
        if (!centers) return {};
        const match = centers.match(/([a-e])([1-5])/i);
        if (!match) return {};
        const column = match[1].toLowerCase();
        const row = parseInt(match[2]);
        const columnMap = {'a': 0.1,'b': 0.3,'c': 0.5,'d': 0.7,'e': 0.9 };
        const rowMap = { 1: 0.1, 2: 0.3, 3: 0.5, 4: 0.7, 5: 0.9 };
        return { x: columnMap[column] || 0.5, y: rowMap[row] || 0.5 };
    }


    async function naiGenerate(button1) {
        if(!button1.id.includes("image")) return;
        button1.textContent="加载中";
        const locationHash = button1.dataset.locationHash; // 获取位置哈希
        if (!xiancheng) await sleep(1000);
        xiancheng = false;

        // --- 公共参数准备 ---
        const dynamic_prompt_raw = button1.dataset.link;
        const negativePrompt = await fumian(settings.negativePrompt, settings.UCP);
        const naiSteps = Number(settings.steps);

        try {
            let dataURL;
            const newImageId = await getNextImageId(); // 获取新的唯一ID

            if (settings.naiChannel === 'official') {
                console.log("NAI Official Request");

                // --- 官方渠道公共参数 ---
                let base_prompt = "";
                let payload;

                // 检查是否为 V4 或更高版本模型
                const isV4PlusModel = settings.naiModel.startsWith('nai-diffusion-4');
                const useCoords = settings.AI_use_coords === 'true';
                const useVibeTransfer = settings.naiVibeTransferEnabled === 'true' && settings.naiVibeTransferImages && settings.naiVibeTransferImages.length > 0;

                if (isV4PlusModel) {
                    // --- V4及以上模型逻辑 ---
                    console.log("NAI v4+ Model Detected. Building new payload structure.");

                    let preset_data = {
                        "params_version": 3,
                        "width": Number(settings.width),
                        "height": Number(settings.height),
                        "scale": Number(settings.naiScale),
                        "sampler": settings.naiSampler,
                        "steps": naiSteps,
                        "n_samples": 1,
                        "ucPreset": 0, // V4中通常不使用ucPreset,设为0或移除
                        "qualityToggle": true,
                        "dynamic_thresholding": settings.nai3Deceisp === 'true',
                        "cfg_rescale": Number(settings.naiCfg),
                        "noise_schedule": settings.naiNoiseSchedule,
                        "seed": Date.now() + Math.floor(Math.random() * 1000),
                        "negative_prompt": negativePrompt,
                    };

                    // 处理Vibe Transfer
                    if (useVibeTransfer) {
                        button1.textContent = "加载中(Vibe)";

                        const modelToVibeKeyMap = {
                            'nai-diffusion-4-full': 'v4full',
                            'nai-diffusion-4-curated-preview': 'v4curated',
                            'nai-diffusion-4-5-full': 'v4-5full',
                            'nai-diffusion-4-5-curated': 'v4-5curated',
                        };
                        const currentVibeKey = modelToVibeKeyMap[settings.naiModel];
                        if (!currentVibeKey) {
                             throw new Error(`当前选择的模型 "${settings.naiModel}" 不支持 V4 Vibe Transfer。`);
                        }

                        const vibes = [];
                        const strengths = [];
                        const errors = [];

                        for (const item of settings.naiVibeTransferImages) {
                            if (item.type === 'vibeFile' && item.vibeData) {
                                const encodingBlock = item.vibeData.encodings[currentVibeKey];
                                if (encodingBlock) {
                                    // Vibe文件结构中,encodingBlock下只有一个键值对
                                    const encodingData = Object.values(encodingBlock)[0];
                                    if(encodingData && encodingData.encoding){
                                        vibes.push(encodingData.encoding);
                                        strengths.push(parseFloat(item.refStrength)); // 使用滑块设置的强度
                                    } else {
                                        errors.push(`文件 "${item.vibeData.name}" 缺少有效的 'encoding' 数据。`);
                                    }
                                } else {
                                    errors.push(`文件 "${item.vibeData.name}" 没有为当前模型 "${settings.naiModel}" 生成的 Vibe 数据。`);
                                }
                            } else {
                                errors.push(`一个普通图片被用于 V4+ Vibe Transfer,这是不兼容的。请上传 .naiv4vibe 文件。`);
                            }
                        }

                        if(errors.length > 0){
                            const errorMsg = `Vibe Transfer 错误:\n\n- ${errors.join('\n- ')}\n\n请去官网为当前模型生成 Vibe 文件并重新上传。`;
                            alert(errorMsg);
                            throw new Error("Vibe Transfer 配置错误。");
                        }

                        // V4+ Vibe Transfer 使用不同的参数名
                        preset_data.reference_image_multiple = vibes;
                        preset_data.reference_strength_multiple = strengths;
                    }

                    if (settings.nai3Variety !== 'false') {
                        if (settings.naiModel.includes('nai-diffusion-4-5')) preset_data.skip_cfg_above_sigma = 59.047226;
                        else if (settings.naiModel.includes('nai-diffusion-4-full')) preset_data.skip_cfg_above_sigma = 19;
                    }

                    const useMultiCharacter = dynamic_prompt_raw.includes("Scene Composition:");

                    if (useMultiCharacter) {
                        const prompt_data = parsePromptStringWithCoordinates(dynamic_prompt_raw);
                        base_prompt = await zhengmian(settings.naiPositivePrompt, prompt_data["Scene Composition"], "");

                        const char_captions_prompt = [];
                        const char_captions_uc = [];
                        for (let i = 1; i <= 4; i++) {
                            if (prompt_data[`Character ${i} Prompt`]) {
                                char_captions_prompt.push({
                                    char_caption: prompt_data[`Character ${i} Prompt`],
                                    centers: [prompt_data[`Character ${i} coordinates`]]
                                });
                                char_captions_uc.push({
                                    char_caption: prompt_data[`Character ${i} UC`] || '',
                                    centers: [prompt_data[`Character ${i} coordinates`]]
                                });
                            }
                        }

                        preset_data.v4_prompt = { caption: { base_caption: base_prompt, char_captions: char_captions_prompt }, use_coords: useCoords, use_order: true };
                        preset_data.v4_negative_prompt = { caption: { base_caption: negativePrompt, char_captions: char_captions_uc } };
                    } else {
                        base_prompt = await zhengmian(settings.naiPositivePrompt, dynamic_prompt_raw, "");
                        preset_data.v4_prompt = { caption: { base_caption: base_prompt, char_captions: [] }, use_coords: useCoords, use_order: true };
                        preset_data.v4_negative_prompt = { caption: { base_caption: negativePrompt, char_captions: [] } };
                    }

                    payload = {
                        "input": base_prompt, // input可以是base_prompt或空字符串
                        "model": settings.naiModel,
                        "action": "generate",
                        "parameters": preset_data
                    };

                } else {
                    // --- V3及以下官方模型逻辑 ---
                    console.log("NAI v3 Model Detected. Building legacy payload structure.");
                    base_prompt = await zhengmian(settings.naiPositivePrompt, dynamic_prompt_raw.replaceAll("\n", ", "), settings.AQT);

                    let preset_data = {
                        "params_version": 3,
                        "width": Number(settings.width),
                        "height": Number(settings.height),
                        "scale": Number(settings.naiScale),
                        "sampler": settings.naiSampler,
                        "steps": naiSteps,
                        "n_samples": 1,
                        "ucPreset": 3,
                        "qualityToggle": true,
                        sm: settings.nai3sm === 'true',
                        "sm_dyn": settings.nai3dyn === 'true' && settings.nai3sm === 'true',
                        "dynamic_thresholding": settings.nai3Deceisp === 'true',
                        "cfg_rescale": Number(settings.naiCfg),
                        "noise_schedule": settings.naiNoiseSchedule,
                        "seed": Date.now() + Math.floor(Math.random() * 1000),
                        "negative_prompt": negativePrompt,
                    };

                    if (useVibeTransfer) {
                        button1.textContent = "加载中(Vibe)";
                        // V3/代理 使用普通图片base64
                        const imageBase64s = settings.naiVibeTransferImages
                            .filter(img => img.type === 'image' && img.base64)
                            .map(img => img.base64.split(',')[1]);

                        if (imageBase64s.length !== settings.naiVibeTransferImages.length){
                            alert("警告:您为 V3 模型上传了 .naiv4vibe 文件,这些文件将被忽略。V3 模式仅支持普通图片。");
                        }

                        if (imageBase64s.length > 0) {
                            preset_data.reference_image_multiple = imageBase64s;
                            preset_data.reference_information_extracted_multiple = settings.naiVibeTransferImages.map(img => parseFloat(img.infoExtract));
                            preset_data.reference_strength_multiple = settings.naiVibeTransferImages.map(img => parseFloat(img.refStrength));
                        }
                    }

                    payload = {
                        "input": base_prompt,
                        "model": settings.naiModel,
                        "action": "generate",
                        "parameters": preset_data
                    };
                }

                console.log("NAI Official (POST) Payload:", JSON.parse(JSON.stringify(payload)));
                const response = await gmXmlHttpRequestPromise({
                    method: "POST", url: settings.naiApiUrl,
                    headers: { "Content-Type": "application/json", "Authorization": `Bearer ${settings.naiToken}` },
                    data: JSON.stringify(payload), responseType: 'arraybuffer'
                });

                if (response.status < 200 || response.status >= 300) {
                    const errorText = response.responseText || "服务器未返回错误信息。";
                    let readableError = errorText;
                    try {
                        const errorJson = JSON.parse(errorText);
                        readableError = `(${errorJson.statusCode}) ${errorJson.message}`;
                    } catch (e) {
                    }
                    throw new Error(`请求失败 (HTTP ${response.status}): ${readableError}`)
                }
                const base64Image = await unzipFile(response.response);
                dataURL = "data:image/png;base64," + base64Image;

            } else {
                // ============ 第三方代理 API 逻辑==========
                console.log("NAI Proxy Request");

                // 新增:检测是否为多角色模式
                const useMultiCharacter = dynamic_prompt_raw.includes("Scene Composition:");
                let response;

                if (useMultiCharacter) {
                    // --- 第三方代理的多角色逻辑 ---
                    console.log("Proxy: Multi-Role mode detected.");
                    button1.textContent = "加载中(角色定位)";

                    const prompt_data = parsePromptStringWithCoordinates(dynamic_prompt_raw);

                    // 构建 multiRoleList
                    const multiRoleList = [];
                    for (let i = 1; i <= 4; i++) {
                        if (prompt_data[`Character ${i} Prompt`]) {
                            multiRoleList.push({
                                prompt: prompt_data[`Character ${i} Prompt`],
                                negativePrompt: prompt_data[`Character ${i} UC`] || '',
                                position: prompt_data[`Character ${i} centers`]
                            });
                        }
                    }

                    // 构建 POST 请求体
                    const multiRolePayload = {
                        token: settings.naiToken,
                        model: settings.naiModel,
                        sampler: settings.naiSampler,
                        noise_schedule: settings.naiNoiseSchedule,
                        size: settings.size === "Custom" ? `${settings.width}x${settings.height}` : settings.size,
                        steps: naiSteps.toString(),
                        scale: settings.naiScale,
                        cfg: settings.naiCfg,
                        nocache: 1,
                        stream: 0, // 多角色模式通常不支持流式
                        tag: await zhengmian(settings.naiPositivePrompt, prompt_data["Scene Composition"], ""),
                        negative: negativePrompt, // 全局负面提示词
                        addition: {
                            imageToImageBase64: null,
                            vibeTransferList: [],
                            multiRoleList: multiRoleList
                        }
                    };

                    console.log("NAI Proxy Multi-Role (POST) Payload:", multiRolePayload);
                    response = await gmXmlHttpRequestPromise({
                        method: "POST",
                        url: settings.naiApiUrl,
                        headers: { "Content-Type": "application/json", "Authorization": `Bearer ${settings.naiToken}` },
                        data: JSON.stringify(multiRolePayload),
                        responseType: 'blob'
                    });

                } else {
                    const positivePrompt = await zhengmian(settings.naiPositivePrompt, dynamic_prompt_raw, settings.AQT);
                    const useVibeTransfer = settings.naiVibeTransferEnabled === 'true' && settings.naiVibeTransferImages && settings.naiVibeTransferImages.length > 0;

                    if (useVibeTransfer) {
                        console.log("Proxy: Vibe-Transfer mode detected.");
                        button1.textContent = "加载中(Vibe)";

                        // 代理仅支持普通图片
                        const vibeListForProxy = settings.naiVibeTransferImages
                            .filter(img => img.type === 'image' && img.base64)
                            .map(img => ({
                                base64: img.base64.split(',')[1],
                                infoExtract: parseFloat(img.infoExtract),
                                refStrength: parseFloat(img.refStrength)
                            }));

                        if (vibeListForProxy.length !== settings.naiVibeTransferImages.length) {
                            alert("警告:您为代理模式上传了 .naiv4vibe 文件,这些文件将被忽略。代理模式仅支持普通图片。");
                        }

                        const vibePayload = {
                            token: settings.naiToken,
                            model: settings.naiModel,
                            sampler: settings.naiSampler,
                            noise_schedule: settings.naiNoiseSchedule,
                            size: settings.size === "Custom" ? `${settings.width}x${settings.height}` : settings.size,
                            steps: naiSteps.toString(),
                            scale: settings.naiScale,
                            cfg: settings.naiCfg,
                            nocache: '1',
                            tag: positivePrompt,
                            negative: negativePrompt,
                            addition: {
                                imageToImageBase64: null,
                                vibeTransferList: vibeListForProxy
                            }
                        };
                        console.log("NAI Vibe Transfer (POST) Payload:", vibePayload);
                        response = await gmXmlHttpRequestPromise({
                            method: "POST",
                            url: settings.naiApiUrl,
                            headers: { "Content-Type": "application/json", "Authorization": `Bearer ${settings.naiToken}` },
                            data: JSON.stringify(vibePayload),
                            responseType: 'blob'
                        });
                    } else {
                        console.log("Proxy: Standard GET request.");
                        const imageUrl = new URL(settings.naiApiUrl);
                        imageUrl.searchParams.set('token', settings.naiToken);
                        imageUrl.searchParams.set('model', settings.naiModel);
                        imageUrl.searchParams.set('sampler', settings.naiSampler);
                        imageUrl.searchParams.set('noise_schedule', settings.naiNoiseSchedule);
                        if (settings.size === "Custom") {
                            imageUrl.searchParams.set('size', `${settings.width}x${settings.height}`);
                        } else {
                            imageUrl.searchParams.set('size', settings.size);
                        }
                        imageUrl.searchParams.set('steps', naiSteps.toString());
                        imageUrl.searchParams.set('scale', settings.naiScale);
                        imageUrl.searchParams.set('cfg', settings.naiCfg);
                        imageUrl.searchParams.set('nocache', '1');
                        imageUrl.searchParams.set('artist', '@');
                        imageUrl.searchParams.set('tag', positivePrompt);

                        if (negativePrompt && negativePrompt.trim() !== '') {
                            imageUrl.searchParams.set('negative', negativePrompt);
                        }

                        console.log("NAI Proxy Request URL (GET):", imageUrl.href);
                        response = await gmXmlHttpRequestPromise({
                            method: "GET",
                            url: imageUrl.href,
                            responseType: 'blob'
                        });
                    }
                }
                // ============= 第三方代理 API 逻辑  ===========

                dataURL = await new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onloadend = () => resolve(reader.result);
                    reader.onerror = reject;
                    reader.readAsDataURL(response.response);
                });
            }

            await setItemImg(newImageId, dataURL, locationHash);

            // ====================== 新增的修改点 ======================
            // 如果是模态框调用,直接返回数据,不进行DOM操作
            if (button1.isModalCall) {
                return dataURL;
            }
            // ====================== 修改结束 ==========================

            let imgSpan = button1.ownerDocument.getElementById(button1.name);
            if (imgSpan) {
                const img = document.createElement('img');
                img.src = dataURL;
                img.alt = "Generated Image";
                img.dataset.name = imgSpan.dataset.name;
                // 【核心修改】当处于兼容模式时,应用自适应样式
                if (settings.displayMode === '兼容前端') {
                    img.style.maxWidth = '100%';
                    img.style.height = 'auto';
                    img.style.minWidth = '50px';
                    img.style.minHeight = '50px';
                    img.style.objectFit = 'contain';
                    img.style.display = 'block';
                    img.style.borderRadius = "5px";
                }
                if (settings.dbclike == "true") {
                    imgSpan.style.textAlign = 'center';
                    button1.style.cssText = 'width: 0; height: 0; overflow: hidden; padding: 0; border: none;';
                }
                imgSpan.replaceChildren(img);
            }

        } catch (error) {
            console.error("NovelAI图片生成请求失败:", error);
            // 如果是模态框调用,不要弹alert,而是返回null让调用者处理
            if (button1.isModalCall) {
                // 抛出异常,让调用方的catch块来处理UI反馈
                throw error;
            }
            alert("NovelAI图片生成请求失败: " + (error.message || "未知错误,请检查控制台"));
        } finally {
            xiancheng = true;
            // 只有在非模态框调用时,才恢复按钮文本
            if (button1.isConnected && !button1.isModalCall) {
                button1.textContent="生成图片";
            }
        }
    }


  // ==================== 修改/新增区域: ComfyUI 动态参数应用函数 ====================
  async function applyDynamicParamsToWorkflow(workflow) {
      console.log("[ComfyUI] Generating with JIT dynamic parameters from saved settings...");

      const findNodeByTitle = (wf, title) => { for (const id in wf) { if (wf[id]?._meta?.title?.includes(title)) return id; } return null; };
      const findNodesByClassType = (wf, classType) => { const ids = []; for (const id in wf) { if (wf[id]?.class_type === classType) ids.push(id); } return ids; };
      const findNode = (wf, titles, classType) => {
          for (const title of titles) { for (const id in wf) { if (wf[id]?._meta?.title?.includes(title)) return [id]; } }
          const ids = [];
          for (const id in wf) { if (wf[id]?.class_type === classType) ids.push(id); }
          return ids;
      };

      // 创建工作流的深拷贝,以避免修改存储中的原始对象
      let dynamicWorkflow = JSON.parse(JSON.stringify(workflow));

      // 1. 注入分辨率
      const resNodeIds = findNode(dynamicWorkflow, ["分辨率", "Latent"], "EmptyLatentImage");
      if (resNodeIds.length > 0) {
          resNodeIds.forEach(id => {
              dynamicWorkflow[id].inputs.width = Number(settings.width);
              dynamicWorkflow[id].inputs.height = Number(settings.height);
          });
      }

      // 2. 注入主采样器参数
      const samplerNodeIds = findNode(dynamicWorkflow, ["采样器", "Sampler"], "KSampler");
      if (samplerNodeIds.length > 0) {
          samplerNodeIds.forEach(id => {
              dynamicWorkflow[id].inputs.steps = Number(settings.steps);
              dynamicWorkflow[id].inputs.cfg = Number(settings.sdCfgScale); // ComfyUI 的 CFG 复用 SD 的设置
              dynamicWorkflow[id].inputs.sampler_name = settings.comfyuiSampler;
              dynamicWorkflow[id].inputs.scheduler = settings.comfyuiScheduler;
          });
      }

      // 3. 注入主模型
      const ckptNodeIds = findNode(dynamicWorkflow, ["主模型", "Checkpoint"], "CheckpointLoaderSimple");
      if (ckptNodeIds.length > 0) {
          ckptNodeIds.forEach(id => dynamicWorkflow[id].inputs.ckpt_name = settings.comfyuiModel);
      }

      // 4. 注入 LoRA 参数
      const loraStackNodeId = findNodeByTitle(dynamicWorkflow, "Lora Loader Stack");
      if (loraStackNodeId) {
           dynamicWorkflow[loraStackNodeId].inputs.lora_01 = settings.comfyuiLora;
           dynamicWorkflow[loraStackNodeId].inputs.strength_01 = parseFloat(settings.comfyuiLoraStrength);
           dynamicWorkflow[loraStackNodeId].inputs.lora_02 = settings.comfyuiLora2;
           dynamicWorkflow[loraStackNodeId].inputs.strength_02 = parseFloat(settings.comfyuiLoraStrength2);
           dynamicWorkflow[loraStackNodeId].inputs.lora_03 = settings.comfyuiLora3;
           dynamicWorkflow[loraStackNodeId].inputs.strength_03 = parseFloat(settings.comfyuiLoraStrength3);
           dynamicWorkflow[loraStackNodeId].inputs.lora_04 = settings.comfyuiLora4;
           dynamicWorkflow[loraStackNodeId].inputs.strength_04 = parseFloat(settings.comfyuiLoraStrength4);
      }

       // 5. 注入 FaceDetailer 参数
       const faceDetailerNodeIds = findNode(dynamicWorkflow, ["FaceDetailer"], "FaceDetailer");
       if(faceDetailerNodeIds.length > 0) {
           faceDetailerNodeIds.forEach(id => {
               if(!dynamicWorkflow[id].inputs) dynamicWorkflow[id].inputs = {};
               dynamicWorkflow[id].inputs.steps = Number(settings.faceDetailerSteps);
               dynamicWorkflow[id].inputs.guide_size = Number(settings.faceDetailerGuideSize);
               dynamicWorkflow[id].inputs.max_size = Number(settings.faceDetailerMaxSize);
               dynamicWorkflow[id].inputs.cfg = Number(settings.faceDetailerCfg);
               dynamicWorkflow[id].inputs.sampler_name = settings.faceDetailerSampler;
               dynamicWorkflow[id].inputs.scheduler = settings.faceDetailerScheduler;
           });
           console.log(`[ComfyUI] Applied FaceDetailer params to node(s) #${faceDetailerNodeIds.join(', ')}`);
       }

       // 6. 注入 Ultimate SD Upscale 参数
       const ultimateUpscaleNodeIds = findNodesByClassType(dynamicWorkflow, "UltimateSDUpscale");
       if (ultimateUpscaleNodeIds.length > 0) {
           const upscaleModelNodeIds = findNodesByClassType(dynamicWorkflow, "UpscaleModelLoader");

           if (upscaleModelNodeIds.length > 0) {
               upscaleModelNodeIds.forEach(id => {
                   if(!dynamicWorkflow[id].inputs) dynamicWorkflow[id].inputs = {};
                   dynamicWorkflow[id].inputs.model_name = settings.comfyuiUpscaleModel;
               });
               console.log(`[ComfyUI] Applied Upscale Model to node(s) #${upscaleModelNodeIds.join(', ')}`);
           }

           ultimateUpscaleNodeIds.forEach(id => {
               if(!dynamicWorkflow[id].inputs) dynamicWorkflow[id].inputs = {};
               dynamicWorkflow[id].inputs.upscale_by = parseFloat(settings.comfyuiUltimateUpscaleBy);
               dynamicWorkflow[id].inputs.steps = Number(settings.comfyuiUltimateSteps);
               dynamicWorkflow[id].inputs.cfg = Number(settings.comfyuiUltimateCfg);
               dynamicWorkflow[id].inputs.sampler_name = settings.comfyuiUltimateSampler;
               dynamicWorkflow[id].inputs.scheduler = settings.comfyuiUltimateScheduler;
               dynamicWorkflow[id].inputs.denoise = parseFloat(settings.comfyuiUltimateDenoise);
               dynamicWorkflow[id].inputs.mode_type = settings.comfyuiUltimateModeType;
           });
           console.log(`[ComfyUI] Applied Ultimate SD Upscale params to node(s) #${ultimateUpscaleNodeIds.join(', ')}`);
       }
       // 7. 注入脸部修复检测模型
       const detectorNodeIds = findNodesByClassType(dynamicWorkflow, "UltralyticsDetectorProvider");
       if(detectorNodeIds.length > 0) {
           detectorNodeIds.forEach(id => {
               if(!dynamicWorkflow[id].inputs) dynamicWorkflow[id].inputs = {};
               dynamicWorkflow[id].inputs.model_name = settings.faceDetailerModel;
           });
           console.log(`[ComfyUI] Applied Face Detailer Detector Model to node(s) #${detectorNodeIds.join(', ')}`);
       }
      return dynamicWorkflow;
  }

  async function comfyuiGenerate(button1) {
      if(!button1.id.includes("image")) return;
      // 在模态框调用时,我们不希望有任何DOM操作,所以textContent的修改也需要考虑
      // 但由于伪按钮并不在DOM中,这个操作是安全的,可以保留作为状态指示
      button1.textContent="加载中";
      const locationHash = button1.dataset.locationHash; // 获取位置哈希
      if(!xiancheng) await sleep(1000);
      xiancheng=false;

    // 辅助函数(仅用于本函数内的特定查找)
    const findNodeByTitle = (wf, title) => { for (const id in wf) { if (wf[id]?._meta?.title?.includes(title)) return id; } return null; };
    const findNodesByClassType = (wf, classType) => { const ids = []; for (const id in wf) { if (wf[id]?.class_type === classType) ids.push(id); } return ids; };

    try {
        const newImageId = await getNextImageId(); // 获取新的唯一ID
        const url = removeTrailingSlash(settings.comfyuiUrl);
        let baseWorkflow;

        // 1. 从设置中加载用户选定的基础工作流
        try {
            baseWorkflow = JSON.parse(settings.comfyuiWorkflows[settings.comfyuiCurrentWorkflow]);
        } catch (e) {
            // 如果是模态框调用,不弹窗,直接抛出错误
            const errorMsg = `当前选定的工作流 "${settings.comfyuiCurrentWorkflow}" 不是有效的JSON格式,无法生成。请在设置中修复或重新创建。`;
            if (button1.isModalCall) throw new Error(errorMsg);
            alert(errorMsg);
            button1.textContent="生成图片";
            xiancheng = true;
            return;
        }

        // 2. 根据“纯净工作流模式”开关状态,决定如何处理工作流
        let workflow;
        if (settings.comfyuiUseCustomWorkflowOnly === 'true') {
            console.log("[ComfyUI] 纯净工作流模式已启用。仅注入提示词和种子。");
            workflow = JSON.parse(JSON.stringify(baseWorkflow));
        } else {
            console.log("[ComfyUI] 标准模式。正在应用所有动态参数。");
            workflow = await applyDynamicParamsToWorkflow(baseWorkflow);
        }

        // 3. 注入本次生成的特定提示词
        let prompt = await zhengmian(settings.fixedPrompt, button1.dataset.link, settings.AQT);
        let negative_prompt = await fumian(settings.negativePrompt,settings.UCP);

        const posPromptNodeId = findNodeByTitle(workflow, "正面提示词");
        if(posPromptNodeId) {
            workflow[posPromptNodeId].inputs.text = prompt;
        } else {
            console.warn("[ComfyUI] 未在工作流中找到'正面提示词'节点,动态提示词未注入。");
        }

        const negPromptNodeId = findNodeByTitle(workflow, "负面提示词");
        if(negPromptNodeId) {
            workflow[negPromptNodeId].inputs.text = negative_prompt;
        } else {
            console.warn("[ComfyUI] 未在工作流中找到'负面提示词'节点,动态负面提示词未注入。");
        }

        // 4. 注入随机种子
        const samplerNodeIds = findNodesByClassType(workflow, "KSampler");
        if (samplerNodeIds.length > 0) {
            samplerNodeIds.forEach(id => {
                if (workflow[id].inputs) {
                    workflow[id].inputs.seed = Date.now() + Math.floor(Math.random() * 1000000);
                }
            });
        }
        const faceDetailerNodeIds = findNodesByClassType(workflow, "FaceDetailer");
        if (faceDetailerNodeIds.length > 0) {
            faceDetailerNodeIds.forEach(id => {
                if (workflow[id].inputs) {
                    workflow[id].inputs.seed = Date.now() + Math.floor(Math.random() * 1000000);
                }
            });
        }
        const ultimateUpscaleNodeIds = findNodesByClassType(workflow, "UltimateSDUpscale");
        if(ultimateUpscaleNodeIds.length > 0){
            ultimateUpscaleNodeIds.forEach(id => {
                if (workflow[id].inputs) {
                    workflow[id].inputs.seed = Date.now() + Math.floor(Math.random() * 1000000);
                }
            });
        }

        // 5. 发送请求
        const clientId = Math.random().toString(36).substring(2);
        const payload = { prompt: workflow, client_id: clientId };
        console.log("[ComfyUI] 最终发送的生成参数:", JSON.parse(JSON.stringify(payload)));

        const resp = await gmXmlHttpRequestPromise({ method: "POST", url: `${url}/prompt`, data: JSON.stringify(payload), headers: {"Content-Type": "application/json"} });
        const queueData = JSON.parse(resp.responseText);

        if (queueData.error) {
            console.error("[ComfyUI] Prompt Error:", queueData);
            let errorDetails = queueData.error.message || '';
            if(queueData.node_errors) {
                for(const node in queueData.node_errors){
                    if(queueData.node_errors[node].errors[0].message){
                        errorDetails += `\n节点 ${node} (${queueData.node_errors[node].class_type}): ${queueData.node_errors[node].errors[0].message}`;
                    }
                }
            }
            throw new Error(`ComfyUI 提交失败: ${queueData.error.type} - ${errorDetails}`);
        }
        if (queueData.prompt_id) {
            const promptId = queueData.prompt_id;
            button1.textContent=`排队中 #${queueData.number}`;

            // ### 轮询结果 ###
            let history = null;
            for (let i = 0; i < 360; i++) { // 3分钟超时
                await sleep(1000);
                const historyResp = await gmXmlHttpRequestPromise({ method: "GET", url: `${url}/history/${promptId}` });
                const historyData = JSON.parse(historyResp.responseText);
                if (Object.keys(historyData).length > 0 && historyData[promptId]) {
                    history = historyData[promptId];
                    break;
                }
            }
            if (!history) throw new Error("图片生成超时");
            if (history.status?.status_str !== 'success' && history.status?.completed !== true) throw new Error(`生成失败: ${history.status?.status_str || '未知状态'}`);

            // ### 查找并获取图片 ###
            let outputImageInfo = null;
            const potentialOutputNodes = findNodesByClassType(workflow, "SaveImage").concat(findNodesByClassType(workflow, "PreviewImage"));
            for (const nodeId of potentialOutputNodes) {
                if (history.outputs[nodeId]?.images?.[0]) {
                    outputImageInfo = history.outputs[nodeId].images[0];
                    break;
                }
            }

            if (!outputImageInfo) {
                throw new Error("在工作流结果中未找到有效的输出节点(如'SaveImage', 'PreviewImage'等),或该节点未生成图片。");
            }
            const imageUrl = `${url}/view?filename=${encodeURIComponent(outputImageInfo.filename)}&subfolder=${encodeURIComponent(outputImageInfo.subfolder)}&type=${outputImageInfo.type}`;
            const imageResponse = await gmXmlHttpRequestPromise({ method: "GET", url: imageUrl, responseType: 'blob' });

            const dataURL = await new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onloadend = () => resolve(reader.result);
                reader.onerror = reject;
                reader.readAsDataURL(imageResponse.response);
            });

            await setItemImg(newImageId, dataURL, locationHash);

            // ====================== 新增的修改点 ======================
            // 如果是模态框调用,直接返回数据,不进行DOM操作
            if (button1.isModalCall) {
                return dataURL;
            }
            // ====================== 修改结束 ==========================

            let imgSpan = button1.ownerDocument.getElementById(button1.name);
            const img = document.createElement('img');
            img.src = dataURL; img.alt = "Generated Image"; img.dataset.name = imgSpan.dataset.name;
            // 【核心修改】当处于兼容模式时,应用自适应样式
            if (settings.displayMode === '兼容前端') {
                img.style.maxWidth = '100%';
                img.style.height = 'auto';
                img.style.minWidth = '50px';
                img.style.minHeight = '50px';
                img.style.objectFit = 'contain';
                img.style.display = 'block';
                img.style.borderRadius = "5px";
            }
            if(settings.dbclike=="true"){
                imgSpan.style.textAlign = 'center';
                button1.style.cssText = 'width: 0; height: 0; overflow: hidden; padding: 0; border: none;';
            }
            imgSpan.replaceChildren(img);
        } else {
            throw new Error("ComfyUI返回的数据中没有prompt_id");
        }

    } catch (error) {
        console.error("ComfyUI图片生成请求失败:", error);
        // 如果是模态框调用,不弹窗,而是抛出错误让调用者处理UI
        if (button1.isModalCall) {
            throw error;
        }
        alert("ComfyUI图片生成请求失败: " + error.message);
    } finally {
        xiancheng = true;
        // 只有在非模态框调用时,才恢复按钮文本
        if (button1.isConnected && !button1.isModalCall) {
            button1.textContent="生成图片";
        }
    }
}



    /**
     * 新的辅助函数,用于串行处理自动点击队列
     * @param {Array<HTMLElement>} buttons - 需要自动点击的按钮元素数组
     */
    async function processAutoClickQueue(buttons) {
        // 如果队列已在运行,则直接返回,防止重复执行
        if (isAutoClicking) {
            console.log("文生图插件:自动点击队列已在运行,本次新任务将等待下一轮。");
            return;
        }

        // 锁定队列,开始处理
        isAutoClicking = true;
        console.log(`文生图插件:开始处理自动点击队列,共 ${buttons.length} 个任务。`);

        try {
            // 使用 for...of 循环来确保任务按顺序执行
            for (const button of buttons) {
                // 再次检查按钮是否仍然存在且需要处理
                if (button.isConnected && button.textContent === '生成图片') {
                    console.log(`自动处理任务: ${button.id}, 提示词: "${button.dataset.link}", CacheID: ${button.dataset.cacheId}`);

                    // 确定要调用的生成器函数
                    let generator;
                    switch (settings.currentMode) {
                        case 'sd':      generator = sd; break;
                        case 'nai':     generator = naiGenerate; break;
                        case 'comfyui': generator = comfyuiGenerate; break;
                        default:
                            console.error(`未知的生成模式: ${settings.currentMode},跳过此任务。`);
                            continue; // 跳到下一个按钮
                    }

                    try {
                        // 直接调用生成函数并等待其完成
                        await generator(button);
                        // 成功后可以短暂等待一下,避免对服务器造成连续冲击
                        await sleep(1000);
                    } catch (error) {
                        console.error(`自动生成图片失败 (按钮ID: ${button.id}):`, error);
                        // 即使失败,也继续处理队列中的下一个任务
                        if (document.body.contains(button)) {
                            button.textContent = "生成失败"; // 标记为失败状态
                        }
                    }
                } else {
                     console.log(`跳过已处理或无效的按钮: ${button.id}`);
                }
            }
        } finally {
            // 所有任务处理完毕(无论成功或失败),解锁队列
            console.log("文生图插件:自动点击队列处理完毕。");
            isAutoClicking = false;
        }
    }

        /**
    * 新增:递归处理节点,深度扫描并替换文本中的标签。
    * 这是 "重兼容模式" 的核心。
    * @param {Node} node - 当前要处理的DOM节点。
    * @param {RegExp} regex - 用于匹配标签的正则表达式。
    * @param {string} stableMessageAnchor - 用于生成位置哈希的稳定锚点。
    * @param {Array} buttonsToProcess - 一个数组,用于收集新创建的按钮信息以便后续处理。
    * @param {number} matchCounter - 用于确保同一消息内多个标签的哈希唯一性。
    * @returns {number} - 返回在此节点下找到并处理的匹配项数量。
    */
    function processNodeRecursively(node, regex, stableMessageAnchor, buttonsToProcess) {
        // 只处理元素节点和文本节点
        if (node.nodeType === Node.ELEMENT_NODE) {
            // 不扫描我们自己生成的按钮、span、图片,或脚本/样式标签
            if (['BUTTON', 'SPAN', 'IMG', 'SCRIPT', 'STYLE'].includes(node.tagName) || node.classList.contains('button_image')) {
                return;
            }
            // 遍历子节点(创建副本以安全地修改DOM)
            const children = Array.from(node.childNodes);
            for (const child of children) {
                processNodeRecursively(child, regex, stableMessageAnchor, buttonsToProcess);
            }
        } else if (node.nodeType === Node.TEXT_NODE) {
            const textContent = node.nodeValue;
            // 重置正则表达式的 lastIndex
            regex.lastIndex = 0;
            if (!regex.test(textContent)) {
                return; // 如果文本节点中没有匹配项,则跳过
            }

            const parent = node.parentNode;
            const fragment = document.createDocumentFragment();
            let lastIndex = 0;
            // 再次重置以用于 replace
            regex.lastIndex = 0;

            let match;
            while ((match = regex.exec(textContent)) !== null) {
                // 1. 添加匹配项之前的文本
                if (match.index > lastIndex) {
                    fragment.appendChild(document.createTextNode(textContent.substring(lastIndex, match.index)));
                }

                // 2. 创建并添加我们的按钮和span
                const promptContent = match[1];
                const link = promptContent.replaceAll("《", "<").replaceAll("》", ">").replaceAll("\n", "");
                const buttonId = "button_image" + Math.random().toString(36).substr(2, 9);
                const spanId = "span_" + buttonId;

                // 使用稳定锚点和内容+计数器生成唯一哈希
                const locationHash = CryptoJS.MD5(stableMessageAnchor + link + buttonsToProcess.length).toString();

                const button = document.createElement('button');
                button.id = buttonId;
                button.name = spanId;
                button.dataset.link = link;
                button.dataset.locationHash = locationHash;
                button.className = 'button_image';
                button.textContent = '生成图片';

                const span = document.createElement('span');
                span.id = spanId;
                span.dataset.name = buttonId;

                fragment.appendChild(button);
                fragment.appendChild(span);

                // 收集新创建的按钮信息,以便稍后检查缓存和自动点击
                buttonsToProcess.push({ button, span, locationHash });

                // 更新下一个搜索的起始位置
                lastIndex = match.index + match[0].length;
            }

            // 3. 添加最后一个匹配项之后的任何剩余文本
            if (lastIndex < textContent.length) {
                fragment.appendChild(document.createTextNode(textContent.substring(lastIndex)));
            }

            // 4. 用我们构建的片段替换原始文本节点
            if (fragment.hasChildNodes()) {
                parent.replaceChild(fragment, node);
            }
        }
    }
    /**
     * 新增:辅助函数,用于等待iframe加载完成。
     * @param {HTMLIFrameElement} iframe - 目标iframe元素。
     * @returns {Promise<void>} - 当iframe加载完成时解析的Promise。
     */
    function waitForIframeLoad(iframe) {
        return new Promise(resolve => {
            if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
                resolve();
            } else {
                iframe.onload = () => resolve();
            }
        });
    }

    async function replaceSpansWithImagesst() {
        if (!settings.scriptEnabled || checkSendBuClass()) return;

        const messageContainers = document.getElementsByClassName("mes_text");
        if (messageContainers.length === 0) return;

        const buttonsToAutoClick = [];
        const regex = new RegExp(`${escapeRegExp(settings.startTag)}([\\s\\S]*?)${escapeRegExp(settings.endTag)}`, 'g');

        // =========================================================================
        // ==================== 核心修改:根据模式选择不同逻辑 ====================
        // =========================================================================

        if (settings.displayMode === '兼容前端') {
            // --- 兼容前端 (深度扫描) 模式 ---
            // 这个模式功能强大,但可能破坏某些前端卡片的结构。
            for (const p of messageContainers) {
                if (p.dataset.processedByGenerator) {
                    continue;
                }

                const messageContainer = p.closest('.mes');
                let stableMessageAnchor;
                if (messageContainer) {
                    const messageId = messageContainer.getAttribute('mesid');
                    const timestamp = messageContainer.getAttribute('timestamp');
                    const charName = messageContainer.getAttribute('ch_name');
                    stableMessageAnchor = `${timestamp}-${charName}-${messageId}`;
                } else {
                    stableMessageAnchor = CryptoJS.MD5(p.innerHTML.substring(0, 100)).toString();
                }

                let targetNode = p;
                let eventTarget = p;

                const iframe = p.querySelector('iframe');
                if (iframe && iframe.contentWindow) {
                    try {
                        await waitForIframeLoad(iframe);
                        targetNode = iframe.contentWindow.document.body;
                        eventTarget = iframe.contentWindow.document.body;
                    } catch (e) {
                        console.error("访问iframe内容时出错 (可能是跨域限制):", e);
                        continue;
                    }
                }

                if (!targetNode || !targetNode.textContent || !targetNode.textContent.includes(settings.startTag)) {
                    continue;
                }

                const newButtons = [];
                processNodeRecursively(targetNode, regex, stableMessageAnchor, newButtons);

                if (newButtons.length > 0) {
                    if (!eventTarget.dataset.handlerAttached) {
                        eventTarget.dataset.handlerAttached = "true";

                        const clickHandler = (e) => {
                            if (e.target.tagName === 'BUTTON' && e.target.classList.contains("button_image")) {
                                const generator = { sd: sd, nai: naiGenerate, comfyui: comfyuiGenerate }[settings.currentMode];
                                if (generator) generator(e.target);
                            }
                        };
                        eventTarget.addEventListener('click', clickHandler);

                        if (settings.dbclike === "true") {
                            const dblClickHandler = (e) => {
                                if (e.target.tagName === 'IMG' && e.target.alt === "Generated Image" && e.target.dataset.name) {
                                    addSmoothShakeEffect(e.target);
                                    const ownerDocument = e.target.ownerDocument;
                                    const button = ownerDocument.getElementById(e.target.dataset.name);
                                    if (button) {
                                        const generator = { sd: sd, nai: naiGenerate, comfyui: comfyuiGenerate }[settings.currentMode];
                                        if (generator) generator(button);
                                    }
                                }
                            };
                            eventTarget.addEventListener('dblclick', dblClickHandler);
                        }
                    }

                    for (const item of newButtons) {
                        const { button, span, locationHash } = item;
                        const imageId = locationToImageIdMap[locationHash];

                        if (imageId) {
                            const cachedImgData = await getItemImg(imageId);
                            if (cachedImgData) {
                                const img = document.createElement('img');
                                img.src = cachedImgData;
                                img.alt = "Generated Image";
                                img.dataset.name = span.dataset.name;
                                img.style.maxWidth = '100%';
                                img.style.height = 'auto';
                                img.style.minWidth = '50px';
                                img.style.minHeight = '50px';
                                img.style.objectFit = 'contain';
                                img.style.display = 'block';
                                img.style.borderRadius = "5px";
                                span.appendChild(img);
                                if (settings.dbclike === "true") {
                                    button.style.display = 'none';
                                }
                            }
                        } else if (settings.zidongdianji === "true") {
                            buttonsToAutoClick.push(button);
                        }
                    }
                }
                p.dataset.processedByGenerator = "true";
            }

        } else {
            // --- 默认 (快速扫描) 模式 ---
            for (const p of messageContainers) {
                if (!p.textContent.includes(settings.startTag) || p.dataset.processedByGenerator) {
                    continue;
                }

                const messageContainer = p.closest('.mes');
                let stableMessageAnchor;
                if (messageContainer) {
                    const messageId = messageContainer.getAttribute('mesid');
                    const timestamp = messageContainer.getAttribute('timestamp');
                    const charName = messageContainer.getAttribute('ch_name');
                    stableMessageAnchor = `${timestamp}-${charName}-${messageId}`;
                } else {
                    stableMessageAnchor = CryptoJS.MD5(p.innerHTML).toString();
                }

                const newButtonsInP = new Set();
                let hasChanges = false;
                let matchCountInP = 0;

                const newHtml = p.innerHTML.replace(regex, (match, content) => {
                    hasChanges = true;
                    const link = content.replaceAll("《", "<").replaceAll("》", ">").replaceAll("\n", "");
                    const buttonId = "button_image" + Math.random().toString(36).substr(2, 9);
                    const spanId = "span_" + buttonId;

                    const locationHash = CryptoJS.MD5(stableMessageAnchor + link + matchCountInP).toString();
                    matchCountInP++;

                    newButtonsInP.add({ buttonId, spanId, locationHash });

                    return `<button id="${buttonId}" name="${spanId}" data-link="${link}" data-location-hash="${locationHash}" class="button_image">生成图片</button><span id="${spanId}" data-name="${buttonId}"></span>`;
                });

                if (hasChanges) {
                    p.innerHTML = newHtml;

                    if (!p.dataset.handlerAttached) {
                        p.dataset.handlerAttached = "true";
                        p.addEventListener('click', (e) => {
                            if (e.target.tagName === 'BUTTON' && e.target.id.startsWith("button_image")) {
                                const generator = { sd: sd, nai: naiGenerate, comfyui: comfyuiGenerate }[settings.currentMode];
                                if(generator) generator(e.target);
                            }
                        });

                        if (settings.dbclike === "true") {
                            p.addEventListener('dblclick', (e) => {
                                if (e.target.alt && e.target.alt === "Generated Image") {
                                    addSmoothShakeEffect(e.target);
                                    const button = document.getElementById(e.target.dataset.name);
                                    if (!button) return;

                                    const generator = { sd: sd, nai: naiGenerate, comfyui: comfyuiGenerate }[settings.currentMode];
                                    if(generator) generator(button);
                                }
                            });
                        }
                    }

                    for (const item of newButtonsInP) {
                        const button = document.getElementById(item.buttonId);
                        if (!button) continue;

                        const imageId = locationToImageIdMap[item.locationHash];
                        if (imageId) {
                            const cachedImgData = await getItemImg(imageId);
                            if (cachedImgData) {
                                const imgSpan = document.getElementById(item.spanId);
                                if (imgSpan) {
                                    const img = document.createElement('img');
                                    img.src = cachedImgData;
                                    img.alt = "Generated Image";
                                    img.dataset.name = imgSpan.dataset.name;
                                    img.style.borderRadius = "5px";
                                    imgSpan.appendChild(img);
                                    if (settings.dbclike === "true") {
                                        button.style.cssText = 'width: 0; height: 0; overflow: hidden; padding: 0; border: none;';
                                    }
                                }
                            }
                        } else if (settings.zidongdianji === "true") {
                            buttonsToAutoClick.push(button);
                        }
                    }
                    newButtonsInP.clear();
                }
                p.dataset.processedByGenerator = "true";
            }
        }

        // ==================== 后续处理(对两种模式都通用) ====================

        for (const p of messageContainers) {
            delete p.dataset.processedByGenerator;
        }

        if (buttonsToAutoClick.length > 0) {
            let finalButtons = buttonsToAutoClick;
            const maxClicks = parseInt(settings.maxAutoClicks, 10);
            if (!isNaN(maxClicks) && maxClicks > 0 && finalButtons.length > maxClicks) {
                console.log(`自动点击任务过多 (${finalButtons.length}个),已根据设置限制为最新的 ${maxClicks} 个。`);
                finalButtons = finalButtons.slice(-maxClicks);
            }
            processAutoClickQueue(finalButtons);
        }
    }

    async function initializeCache() {
        console.log("文生图插件:开始初始化缓存系统...");
        try {
            // 1. 初始化全局ID计数器
            globalImageCounter = await GM_getValue('globalImageCounter', 0);

            // 2. 加载位置到ID的映射
            const mapData = await Storereadonly('locationMap');
            if (mapData && mapData.data) {
                locationToImageIdMap = mapData.data;
            }

            // 3. 清理过期的图片
            const allImages = await StoreGetAll();
            const now = new Date().getTime();
            const expiredIds = [];
            let mapNeedsUpdate = false;

            allImages.forEach(image => {
                if (delDate(image.timestamp, now)) {
                    expiredIds.push(image.id);
                }
            });

            if (expiredIds.length > 0) {
                console.log(`清理 ${expiredIds.length} 个过期缓存...`);
                // 并行删除过期的图片
                await Promise.all(expiredIds.map(id => Storedelete(id)));

                // 从映射表中移除对过期ID的引用
                for (const locationHash in locationToImageIdMap) {
                    if (expiredIds.includes(locationToImageIdMap[locationHash])) {
                        delete locationToImageIdMap[locationHash];
                        mapNeedsUpdate = true;
                    }
                }

                if (mapNeedsUpdate) {
                    await Storereadwrite({ id: 'locationMap', data: locationToImageIdMap });
                }
            }

            console.log(`文生图插件:缓存初始化完成。当前总图片数: ${allImages.length - expiredIds.length}。`);

        } catch (error) {
            console.error("文生图插件:缓存初始化失败!", error);
            // 出错时重置,防止后续连锁错误
            globalImageCounter = 0;
            locationToImageIdMap = {};
        }
    }

    async function setItemImg(imageId, imgData, locationHash) {
        // 1. 创建图片数据对象
        const imageRecord = {
            id: imageId,
            imageData: imgData,
            timestamp: new Date().getTime()
        };

        // 2. 更新位置映射
        locationToImageIdMap[locationHash] = imageId;

        // 3. 并行写入数据库
        await Promise.all([
            Storereadwrite(imageRecord), // 写入图片数据
            Storereadwrite({ id: 'locationMap', data: locationToImageIdMap }) // 更新映射表
        ]);
    };

    async function getItemImg(imageId) {
        const imageData = await Storereadonly(imageId);
        return imageData ? imageData.imageData : false; // 返回图片Base64数据
    };

    function delDate(timestamp, now = new Date().getTime()){
        if (Number(settings.cache) === 0) return false;
        // 使用传入的时间戳进行比较
        return (now - Number(timestamp)) > (Number(settings.cache) * 86400000);
    }

  async function zhengmian(text,prom,AQT) { return [text, prom, AQT].filter(Boolean).join(', '); }
  async function fumian(text,UCP){ return [UCP, text].filter(Boolean).join(', '); }
  function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
  function addSmoothShakeEffect(imgEl) {
    if (getComputedStyle(imgEl).position === 'static') imgEl.style.position = 'relative';
    const start = Date.now(), duration = 300, amp = 3;
    function shake() { const el = Date.now() - start; if (el < duration) { imgEl.style.left = `${amp * Math.sin(el/duration*Math.PI*10)}px`; requestAnimationFrame(shake); } else { imgEl.style.left = '0px'; } }
    requestAnimationFrame(shake);
  }

  async function processGenerateImageRequest(reqId, prompt, width, height) {
      if (settings.currentMode !== 'sd') {
          console.warn("processGenerateImageRequest is only compatible with SD mode.");
          return;
      }
      const btn = document.createElement('button');
      btn.id = "compat_btn_" + reqId;
      btn.dataset.link = prompt.replaceAll(/[《》\n]/g, (m) => ({'《':'<','》':'>','\n':','}[m]));
      let imgData = await sd(btn, width, height);
      await sendGenerateImageResponse(reqId, imgData);
  }
  async function sendGenerateImageResponse(reqId, response) {
      if(!response) return;
      try { localStorage.setItem("generate_image_response_"+reqId, JSON.stringify({imageData: response})); } catch (e) { console.error('油猴发送响应失败:', e); }
  }
  function sendGenerateTagsResponse() {
    const resp={"value":settings.startTag,"value2":settings.endTag}, id=Date.now().toString(36);
    try { localStorage.setItem("generate_image_response_tags"+id, JSON.stringify({imageData:resp})); setTimeout(()=>localStorage.removeItem("generate_image_response_tags"+id), 1000); } catch(e){}
  }
  function deleteStorageByKeywords() {
    for (let i = localStorage.length - 1; i >= 0; i--) { const key = localStorage.key(i); if (key.includes('generate_image_response') || key.includes('messageDetails')) localStorage.removeItem(key); }
  }
  unsafeWindow.addEventListener('storage', (e) => {
    if (e.key.startsWith("generate_image_request_")) { try { const req = JSON.parse(e.newValue); localStorage.removeItem(e.key); processGenerateImageRequest(req.id, req.prompt, req.width, req.height); } catch(err){} }
  });
  deleteStorageByKeywords();
  sendGenerateTagsResponse();
  })();