// cloud-presets.js // 云端预设管理模块 (保持大尺寸 + 分页搜索) // ═══════════════════════════════════════════════════════════════════════════ // 常量 // ═══════════════════════════════════════════════════════════════════════════ const CLOUD_PRESETS_API = 'https://draw.velure.top/'; const PLUGIN_KEY = 'xbaix'; const ITEMS_PER_PAGE = 8; // ═══════════════════════════════════════════════════════════════════════════ // 状态 // ═══════════════════════════════════════════════════════════════════════════ let modalElement = null; let allPresets = []; let filteredPresets = []; let currentPage = 1; let onImportCallback = null; // ═══════════════════════════════════════════════════════════════════════════ // API 调用 // ═══════════════════════════════════════════════════════════════════════════ export async function fetchCloudPresets() { const response = await fetch(CLOUD_PRESETS_API, { method: 'GET', headers: { 'Accept': 'application/json', 'X-Plugin-Key': PLUGIN_KEY, 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' }, cache: 'no-store' }); if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); const data = await response.json(); return data.items || []; } export async function downloadPreset(url) { const response = await fetch(url); if (!response.ok) throw new Error(`下载失败: ${response.status}`); const data = await response.json(); if (data.type !== 'novel-draw-preset' || !data.preset) { throw new Error('无效的预设文件格式'); } return data; } // ═══════════════════════════════════════════════════════════════════════════ // 预设处理 // ═══════════════════════════════════════════════════════════════════════════ export function parsePresetData(data, generateId) { const DEFAULT_PARAMS = { model: 'nai-diffusion-4-5-full', sampler: 'k_euler_ancestral', scheduler: 'karras', steps: 28, scale: 6, width: 1216, height: 832, seed: -1, qualityToggle: true, autoSmea: false, ucPreset: 0, cfg_rescale: 0, variety_boost: false, sm: false, sm_dyn: false, decrisper: false, }; return { id: generateId(), name: data.name || data.preset.name || '云端预设', positivePrefix: data.preset.positivePrefix || '', negativePrefix: data.preset.negativePrefix || '', params: { ...DEFAULT_PARAMS, ...(data.preset.params || {}) } }; } export function exportPreset(preset) { const author = prompt("请输入你的作者名:", "") || ""; const description = prompt("简介 (画风介绍):", "") || ""; return { type: 'novel-draw-preset', version: 1, exportDate: new Date().toISOString(), name: preset.name, author: author, 简介: description, preset: { positivePrefix: preset.positivePrefix, negativePrefix: preset.negativePrefix, params: { ...preset.params } } }; } // ═══════════════════════════════════════════════════════════════════════════ // 样式 - 保持原始大尺寸 // ═══════════════════════════════════════════════════════════════════════════ function escapeHtml(str) { return String(str || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function ensureStyles() { if (document.getElementById('cloud-presets-styles')) return; const style = document.createElement('style'); style.id = 'cloud-presets-styles'; style.textContent = ` /* ═══════════════════════════════════════════════════════════════════════════ 云端预设弹窗 - 保持大尺寸,接近 iframe 的布局 ═══════════════════════════════════════════════════════════════════════════ */ .cloud-presets-overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; z-index: 100001 !important; display: flex !important; align-items: center !important; justify-content: center !important; background: rgba(0, 0, 0, 0.85) !important; touch-action: none; -webkit-overflow-scrolling: touch; animation: cloudFadeIn 0.2s ease; } @keyframes cloudFadeIn { from { opacity: 0; } to { opacity: 1; } } /* ═══════════════════════════════════════════════════════════════════════════ 弹窗主体 - 桌面端 80% 高度,宽度增加以适应网格 ═══════════════════════════════════════════════════════════════════════════ */ .cloud-presets-modal { background: #161b22; border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; /* 大尺寸 - 比原来更宽以适应网格 */ width: calc(100vw - 48px); max-width: 800px; height: 80vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.5); } /* ═══════════════════════════════════════════════════════════════════════════ 手机端 - 接近全屏(和 iframe 一样) ═══════════════════════════════════════════════════════════════════════════ */ @media (max-width: 768px) { .cloud-presets-modal { width: 100vw; height: 100vh; max-width: none; border-radius: 0; border: none; } } /* ═══════════════════════════════════════════════════════════════════════════ 头部 ═══════════════════════════════════════════════════════════════════════════ */ .cp-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.1); flex-shrink: 0; background: #0d1117; } .cp-title { font-size: 16px; font-weight: 600; color: #e6edf3; display: flex; align-items: center; gap: 10px; } .cp-title i { color: #d4a574; } .cp-close { width: 40px; height: 40px; min-width: 40px; border: none; background: rgba(255,255,255,0.1); color: #e6edf3; cursor: pointer; border-radius: 8px; font-size: 20px; display: flex; align-items: center; justify-content: center; transition: background 0.15s; -webkit-tap-highlight-color: transparent; } .cp-close:hover, .cp-close:active { background: rgba(255,255,255,0.2); } /* ═══════════════════════════════════════════════════════════════════════════ 搜索栏 ═══════════════════════════════════════════════════════════════════════════ */ .cp-search { padding: 12px 20px; background: #161b22; border-bottom: 1px solid rgba(255,255,255,0.05); flex-shrink: 0; } .cp-search-input { width: 100%; background: #0d1117; border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 12px 16px; color: #e6edf3; font-size: 14px; outline: none; transition: border-color 0.15s; } .cp-search-input::placeholder { color: #484f58; } .cp-search-input:focus { border-color: rgba(212,165,116,0.5); } /* ═══════════════════════════════════════════════════════════════════════════ 内容区域 - 填满剩余空间 ═══════════════════════════════════════════════════════════════════════════ */ .cp-body { flex: 1; overflow-y: auto; padding: 20px; -webkit-overflow-scrolling: touch; background: #0d1117; } /* ═══════════════════════════════════════════════════════════════════════════ 网格布局 ═══════════════════════════════════════════════════════════════════════════ */ .cp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; } @media (max-width: 500px) { .cp-grid { grid-template-columns: 1fr; gap: 12px; } } /* ═══════════════════════════════════════════════════════════════════════════ 卡片样式 ═══════════════════════════════════════════════════════════════════════════ */ .cp-card { background: #21262d; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; gap: 12px; transition: all 0.2s; } .cp-card:hover { border-color: rgba(212,165,116,0.5); transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3); } .cp-card-head { display: flex; align-items: center; gap: 12px; } .cp-icon { width: 44px; height: 44px; background: rgba(212,165,116,0.15); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; } .cp-meta { flex: 1; min-width: 0; overflow: hidden; } .cp-name { font-weight: 600; font-size: 14px; color: #e6edf3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; } .cp-author { font-size: 12px; color: #8b949e; display: flex; align-items: center; gap: 5px; } .cp-author i { font-size: 10px; opacity: 0.7; } .cp-desc { font-size: 12px; color: #6e7681; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: 36px; } .cp-btn { width: 100%; padding: 10px 14px; margin-top: auto; border: 1px solid rgba(212,165,116,0.4); background: rgba(212,165,116,0.12); color: #d4a574; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; justify-content: center; gap: 6px; -webkit-tap-highlight-color: transparent; } .cp-btn:hover { background: #d4a574; color: #0d1117; border-color: #d4a574; } .cp-btn:active { transform: scale(0.98); } .cp-btn:disabled { opacity: 0.5; cursor: not-allowed; } .cp-btn.success { background: #238636; border-color: #238636; color: #fff; } .cp-btn.error { background: #da3633; border-color: #da3633; color: #fff; } /* ═══════════════════════════════════════════════════════════════════════════ 分页控件 ═══════════════════════════════════════════════════════════════════════════ */ .cp-pagination { display: flex; align-items: center; justify-content: center; gap: 16px; padding: 16px 20px; border-top: 1px solid rgba(255,255,255,0.1); background: #161b22; flex-shrink: 0; } .cp-page-btn { padding: 10px 18px; min-height: 40px; background: #21262d; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #e6edf3; cursor: pointer; font-size: 13px; transition: all 0.15s; display: flex; align-items: center; gap: 6px; -webkit-tap-highlight-color: transparent; } .cp-page-btn:hover:not(:disabled) { background: #30363d; border-color: rgba(255,255,255,0.2); } .cp-page-btn:disabled { opacity: 0.35; cursor: not-allowed; } .cp-page-info { font-size: 14px; color: #8b949e; min-width: 70px; text-align: center; font-variant-numeric: tabular-nums; } /* ═══════════════════════════════════════════════════════════════════════════ 状态提示 ═══════════════════════════════════════════════════════════════════════════ */ .cp-loading, .cp-error, .cp-empty { text-align: center; padding: 60px 20px; color: #8b949e; } .cp-loading i { font-size: 36px; color: #d4a574; margin-bottom: 16px; display: block; } .cp-empty i { font-size: 48px; opacity: 0.4; margin-bottom: 16px; display: block; } .cp-empty p { font-size: 12px; margin-top: 8px; opacity: 0.6; } .cp-error { color: #f85149; } /* ═══════════════════════════════════════════════════════════════════════════ 触摸优化 ═══════════════════════════════════════════════════════════════════════════ */ @media (hover: none) and (pointer: coarse) { .cp-close { width: 44px; height: 44px; } .cp-search-input { min-height: 48px; padding: 14px 16px; } .cp-btn { min-height: 48px; padding: 12px 16px; } .cp-page-btn { min-height: 44px; padding: 12px 20px; } } `; document.head.appendChild(style); } // ═══════════════════════════════════════════════════════════════════════════ // UI 逻辑 // ═══════════════════════════════════════════════════════════════════════════ function createModal() { ensureStyles(); const overlay = document.createElement('div'); overlay.className = 'cloud-presets-overlay'; // Template-only UI markup. // eslint-disable-next-line no-unsanitized/property overlay.innerHTML = `