// ==UserScript== // @name AI绘画Tag管理器(终极版-自定义分类) // @namespace http://tampermonkey.net/ // @version 4.0 // @description 储存AI绘画的tag组合,支持自定义分类、搜索和复制,抽屉式界面,黑暗/明亮模式,数据导入导出 // @author salty // @license MIT // @match https://novelai.net/image* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @run-at document-body // @downloadURL none // ==/UserScript== /* 全局变量 */ let db; const DB_NAME = 'AITagsDatabase'; const STORE_NAME = 'tagCollections'; const CATEGORY_STORE = 'tagCategories'; const DB_VERSION = 3; // 升级版本以支持新的存储对象 // 明亮模式主题颜色 const LIGHT_THEME = { primary: '#4f46e5', // 靛蓝色 secondary: '#6d28d9', // 紫色 success: '#10b981', // 绿色 danger: '#ef4444', // 红色 info: '#3b82f6', // 蓝色 warning: '#f59e0b', // 橙色 background: '#f8fafc', // 浅灰背景 surface: '#ffffff', // 表面白色 text: '#1e293b', // 深蓝灰文字 textSecondary: '#64748b', // 次要文字 border: '#e2e8f0', // 边框色 }; // 黑暗模式主题颜色 const DARK_THEME = { primary: '#6366f1', // 靛蓝色 secondary: '#a855f7', // 紫色 success: '#34d399', // 绿色 danger: '#f87171', // 红色 info: '#60a5fa', // 蓝色 warning: '#fbbf24', // 橙色 background: '#0f172a', // 深蓝黑背景 surface: '#1e293b', // 表面深色 text: '#f1f5f9', // 浅色文字 textSecondary: '#94a3b8', // 次要文字 border: '#334155', // 边框色 }; // 默认分类 const DEFAULT_CATEGORIES = [ { id: 'character', name: '角色', color: '#8b5cf6', order: 1 }, { id: 'scene', name: '场景', color: '#0ea5e9', order: 2 }, { id: 'action', name: '动作', color: '#f43f5e', order: 3 }, { id: 'singletag', name: '单tag', color: '#10b981', order: 4 }, { id: 'artist', name: '画师', color: '#f59e0b', order: 5 } ]; /* 主函数 */ (function() { 'use strict'; console.log("======== AI绘画Tag管理器开始加载 ========"); // 获取用户偏好 const isDarkMode = GM_getValue('ai_tag_manager_darkmode', false); const userTheme = GM_getValue('ai_tag_manager_theme', isDarkMode ? DARK_THEME : LIGHT_THEME); // 合并主题和用户自定义颜色 const theme = isDarkMode ? {...DARK_THEME, ...userTheme} : {...LIGHT_THEME, ...userTheme}; /* 1. 添加CSS样式 */ function addStyles(theme) { GM_addStyle(` /* 基本样式 */ :root { --primary-color: ${theme.primary}; --primary-hover: ${adjustColor(theme.primary, -20)}; --secondary-color: ${theme.secondary}; --secondary-hover: ${adjustColor(theme.secondary, -20)}; --danger-color: ${theme.danger}; --danger-hover: ${adjustColor(theme.danger, -20)}; --success-color: ${theme.success}; --success-hover: ${adjustColor(theme.success, -20)}; --info-color: ${theme.info}; --info-hover: ${adjustColor(theme.info, -20)}; --warning-color: ${theme.warning}; --warning-hover: ${adjustColor(theme.warning, -20)}; --background-color: ${theme.background}; --surface-color: ${theme.surface}; --text-color: ${theme.text}; --text-secondary: ${theme.textSecondary}; --border-color: ${theme.border}; } /* 动画定义 */ @keyframes ai-tag-spin { to { transform: rotate(360deg); } } @keyframes ai-tag-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes ai-tag-slide-in { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes ai-tag-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } /* 抽屉手柄 */ #ai-tag-manager-handle { position: fixed; top: 50%; right: 0; transform: translateY(-50%); width: 44px; height: 110px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); border-radius: 12px 0 0 12px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: -2px 2px 10px rgba(0, 0, 0, 0.15); z-index: 9998; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); touch-action: none; } #ai-tag-manager-handle:hover { width: 48px; box-shadow: -3px 3px 15px rgba(0, 0, 0, 0.2); } #ai-tag-manager-handle:active { cursor: grabbing; background: linear-gradient(135deg, var(--primary-hover), var(--secondary-hover)); } #ai-tag-manager-handle::before { content: "≡"; color: white; font-size: 24px; font-weight: bold; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } /* 抽屉容器 */ #ai-tag-manager-drawer { position: fixed; top: 0; right: -350px; width: 320px; height: 100%; background-color: var(--surface-color); box-shadow: -3px 0 20px rgba(0, 0, 0, 0.15); z-index: 9999; transition: right 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); display: flex; flex-direction: column; overflow: hidden; border-radius: 16px 0 0 16px; } #ai-tag-manager-drawer.open { right: 0; } /* 头部 */ #ai-tag-manager-header { padding: 18px 20px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 16px 0 0 0; } #ai-tag-manager-title { font-size: 18px; font-weight: bold; margin: 0; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } #ai-tag-manager-close { font-size: 24px; cursor: pointer; color: white; opacity: 0.8; transition: all 0.2s ease; } #ai-tag-manager-close:hover { opacity: 1; transform: scale(1.1); } #ai-tag-manager-settings { font-size: 18px; cursor: pointer; color: white; opacity: 0.8; margin-right: 15px; transition: all 0.2s ease; } #ai-tag-manager-settings:hover { opacity: 1; transform: rotate(30deg); } #ai-tag-manager-mode-toggle { font-size: 18px; cursor: pointer; color: white; opacity: 0.8; margin-right: 15px; transition: all 0.2s ease; } #ai-tag-manager-mode-toggle:hover { opacity: 1; transform: scale(1.1); } /* 搜索框 */ #ai-tag-manager-search { padding: 15px 20px; background-color: var(--background-color); border-bottom: 1px solid var(--border-color); } #ai-tag-search-input { width: 100%; padding: 10px 15px; border: 1px solid var(--border-color); border-radius: 25px; font-size: 14px; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); background-color: var(--surface-color); color: var(--text-color); } #ai-tag-search-input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); } /* 标签页 */ #ai-tag-manager-tabs { display: flex; flex-wrap: wrap; background-color: var(--background-color); border-bottom: 1px solid var(--border-color); padding: 0 5px; overflow-x: auto; scrollbar-width: thin; max-height: 120px; } #ai-tag-manager-tabs::-webkit-scrollbar { height: 4px; width: 4px; } #ai-tag-manager-tabs::-webkit-scrollbar-thumb { background-color: var(--border-color); border-radius: 2px; } .ai-tag-tab { flex: none; padding: 10px 12px; text-align: center; cursor: pointer; font-size: 14px; color: var(--text-secondary); border-bottom: 2px solid transparent; transition: all 0.3s ease; position: relative; overflow: hidden; margin: 0 2px; white-space: nowrap; } .ai-tag-tab.active { color: var(--primary-color); font-weight: bold; } .ai-tag-tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40%; height: 3px; background-color: var(--primary-color); border-radius: 3px 3px 0 0; transition: all 0.3s ease; } .ai-tag-tab:hover::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 20%; height: 2px; background-color: var(--primary-color); border-radius: 3px 3px 0 0; } /* 内容区域 */ #ai-tag-manager-content { flex: 1; overflow-y: auto; padding: 0; background-color: var(--surface-color); } /* 添加按钮 */ #ai-tag-add-button { position: absolute; bottom: 25px; right: 25px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; font-size: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); z-index: 10000; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } #ai-tag-add-button:hover { transform: scale(1.05) rotate(90deg); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.25); } /* 标签项 */ .ai-tag-collection-item { padding: 15px 20px; border-bottom: 1px solid var(--border-color); cursor: pointer; transition: all 0.2s ease; display: flex; justify-content: space-between; align-items: center; animation: ai-tag-slide-in 0.3s ease; background-color: var(--surface-color); } .ai-tag-collection-item:hover { background-color: var(--background-color); transform: translateX(-5px); } .ai-tag-collection-name { font-weight: 500; color: var(--text-color); position: relative; padding-left: 12px; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ai-tag-collection-name::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 4px; height: 70%; border-radius: 2px; transition: all 0.2s ease; } .ai-tag-collection-actions { display: flex; gap: 8px; } .ai-tag-action-btn { width: 36px; height: 36px; border-radius: 50%; border: none; background-color: transparent; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; font-size: 16px; } .ai-tag-action-btn:hover { background-color: rgba(0, 0, 0, 0.05); color: var(--primary-color); transform: scale(1.1); } /* 详情视图 */ #ai-tag-detail-view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--surface-color); z-index: 10001; display: none; flex-direction: column; border-radius: 16px 0 0 16px; animation: ai-tag-fade-in 0.3s ease; } #ai-tag-detail-header { padding: 18px 20px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 16px 0 0 0; } #ai-tag-detail-title { font-size: 18px; font-weight: bold; margin: 0; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } #ai-tag-detail-back { font-size: 24px; cursor: pointer; color: white; opacity: 0.8; transition: all 0.2s ease; } #ai-tag-detail-back:hover { opacity: 1; transform: scale(1.1); } #ai-tag-detail-content { flex: 1; padding: 20px; overflow-y: auto; } .ai-tag-detail-form-group { margin-bottom: 20px; } .ai-tag-detail-form-group label { display: block; margin-bottom: 8px; font-weight: 500; color: var(--text-color); font-size: 15px; } .ai-tag-detail-form-group input, .ai-tag-detail-form-group textarea, .ai-tag-detail-form-group select { width: 100%; padding: 12px 15px; border: 1px solid var(--border-color); border-radius: 10px; font-size: 15px; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); background-color: var(--surface-color); color: var(--text-color); } .ai-tag-detail-form-group input:focus, .ai-tag-detail-form-group textarea:focus, .ai-tag-detail-form-group select:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); } .ai-tag-detail-form-group textarea { height: 150px; resize: vertical; line-height: 1.5; } .ai-tag-detail-actions { display: flex; gap: 12px; margin-top: 25px; } .ai-tag-btn { padding: 10px 20px; border: none; border-radius: 25px; cursor: pointer; font-size: 15px; font-weight: 500; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .ai-tag-btn-primary { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; } .ai-tag-btn-primary:hover { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); transform: translateY(-2px); } .ai-tag-btn-secondary { background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)); color: white; } .ai-tag-btn-secondary:hover { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); transform: translateY(-2px); } .ai-tag-btn-danger { background: linear-gradient(135deg, var(--danger-color), ${adjustColor(theme.danger, -20)}); color: white; } .ai-tag-btn-danger:hover { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); transform: translateY(-2px); } .ai-tag-btn-default { background-color: var(--background-color); color: var(--text-color); border: 1px solid var(--border-color); } .ai-tag-btn-default:hover { background-color: var(--surface-color); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } /* 空状态 */ .ai-tag-empty-state { text-align: center; padding: 40px 20px; color: var(--text-secondary); animation: ai-tag-fade-in 0.3s ease; background-color: var(--surface-color); } .ai-tag-empty-state p { margin-bottom: 20px; font-size: 15px; } .ai-tag-empty-icon { font-size: 40px; color: var(--secondary-color); opacity: 0.5; margin-bottom: 15px; } /* 移动设备优化 */ @media (max-width: 768px) { #ai-tag-manager-drawer { width: 90%; right: -90%; } #ai-tag-action-btn { width: 32px; height: 32px; font-size: 14px; } } /* 搜索结果高亮 */ .ai-tag-highlight { background-color: rgba(253, 224, 71, 0.3); padding: 0 2px; border-radius: 3px; color: var(--text-color); } /* 加载动画 */ .ai-tag-loading { display: flex; justify-content: center; align-items: center; padding: 30px; flex-direction: column; background-color: var(--surface-color); } .ai-tag-loading-spinner { width: 40px; height: 40px; border: 3px solid rgba(0, 0, 0, 0.1); border-radius: 50%; border-top-color: var(--primary-color); animation: ai-tag-spin 1s ease-in-out infinite; } .ai-tag-loading-text { margin-top: 15px; color: var(--text-secondary); font-size: 14px; } /* 轻提示 */ .ai-tag-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.75); color: white; padding: 10px 20px; border-radius: 25px; z-index: 10002; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); font-size: 14px; animation: ai-tag-fade-in 0.2s ease; } /* 设置面板 */ #ai-tag-settings-view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--surface-color); z-index: 10002; display: none; flex-direction: column; border-radius: 16px 0 0 16px; animation: ai-tag-fade-in 0.3s ease; } #ai-tag-settings-header { padding: 18px 20px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 16px 0 0 0; } #ai-tag-settings-title { font-size: 18px; font-weight: bold; margin: 0; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } #ai-tag-settings-back { font-size: 24px; cursor: pointer; color: white; opacity: 0.8; transition: all 0.2s ease; } #ai-tag-settings-back:hover { opacity: 1; transform: scale(1.1); } #ai-tag-settings-content { flex: 1; padding: 20px; overflow-y: auto; color: var(--text-color); } .ai-tag-settings-section { margin-bottom: 25px; background-color: var(--background-color); border-radius: 12px; padding: 15px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); } .ai-tag-settings-section-title { font-size: 16px; font-weight: 600; color: var(--text-color); margin-bottom: 15px; padding-bottom: 5px; border-bottom: 1px solid var(--border-color); } .ai-tag-color-option { display: flex; align-items: center; margin-bottom: 15px; } .ai-tag-color-option label { flex: 1; font-size: 14px; color: var(--text-color); } .ai-tag-color-picker { width: 30px; height: 30px; border: none; border-radius: 50%; overflow: hidden; cursor: pointer; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .ai-tag-color-picker::-webkit-color-swatch-wrapper { padding: 0; } .ai-tag-color-picker::-webkit-color-swatch { border: none; border-radius: 50%; } .ai-tag-save-theme-btn { width: 100%; padding: 12px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; border: none; border-radius: 25px; font-size: 15px; font-weight: 500; cursor: pointer; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); transition: all 0.3s ease; margin-top: 10px; } .ai-tag-save-theme-btn:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); transform: translateY(-2px); } .ai-tag-reset-theme-btn { width: 100%; padding: 12px; background: transparent; color: var(--text-secondary); border: 1px solid var(--border-color); border-radius: 25px; font-size: 15px; cursor: pointer; transition: all 0.3s ease; margin-top: 10px; } .ai-tag-reset-theme-btn:hover { background-color: var(--background-color); color: var(--text-color); } /* 数据导入导出部分 */ .ai-tag-import-export { display: flex; gap: 10px; margin-top: 15px; } .ai-tag-import-export .ai-tag-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; } .ai-tag-file-input { display: none; } /* 暗黑模式开关 */ .ai-tag-toggle-switch { position: relative; display: flex; align-items: center; cursor: pointer; margin: 15px 0; } .ai-tag-toggle-switch input { opacity: 0; width: 0; height: 0; } .ai-tag-toggle-label { flex: 1; font-size: 14px; color: var(--text-color); } .ai-tag-slider { position: relative; display: inline-block; width: 50px; height: 26px; background-color: #ccc; border-radius: 34px; transition: .4s; } .ai-tag-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 4px; bottom: 4px; background-color: white; border-radius: 50%; transition: .4s; } input:checked + .ai-tag-slider { background-color: var(--primary-color); } input:focus + .ai-tag-slider { box-shadow: 0 0 1px var(--primary-color); } input:checked + .ai-tag-slider:before { transform: translateX(24px); } .ai-tag-slider-icon { position: absolute; top: 50%; transform: translateY(-50%); font-size: 12px; color: white; z-index: 1; } .ai-tag-slider-icon.sun { right: 6px; } .ai-tag-slider-icon.moon { left: 6px; display: none; } input:checked ~ .ai-tag-slider-icon.sun { display: none; } input:checked ~ .ai-tag-slider-icon.moon { display: block; } /* 分类管理视图 */ #ai-tag-categories-view { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--surface-color); z-index: 10003; display: none; flex-direction: column; border-radius: 16px 0 0 16px; animation: ai-tag-fade-in 0.3s ease; } #ai-tag-categories-header { padding: 18px 20px; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 16px 0 0 0; } #ai-tag-categories-title { font-size: 18px; font-weight: bold; margin: 0; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } #ai-tag-categories-back { font-size: 24px; cursor: pointer; color: white; opacity: 0.8; transition: all 0.2s ease; } #ai-tag-categories-back:hover { opacity: 1; transform: scale(1.1); } #ai-tag-categories-content { flex: 1; padding: 20px; overflow-y: auto; } #ai-tag-categories-list { margin-bottom: 20px; } .ai-tag-category-item { display: flex; align-items: center; padding: 12px 15px; margin-bottom: 10px; background-color: var(--background-color); border-radius: 10px; transition: all 0.2s ease; } .ai-tag-category-color { width: 25px; height: 25px; border-radius: 50%; margin-right: 15px; flex-shrink: 0; } .ai-tag-category-info { flex: 1; } .ai-tag-category-name { font-weight: 500; font-size: 15px; color: var(--text-color); margin: 0; margin-bottom: 3px; } .ai-tag-category-id { font-size: 12px; color: var(--text-secondary); margin: 0; } .ai-tag-category-actions { display: flex; gap: 8px; } .ai-tag-category-btn { width: 30px; height: 30px; border-radius: 50%; border: none; background-color: transparent; color: var(--text-secondary); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; font-size: 14px; } .ai-tag-category-btn:hover { background-color: rgba(0, 0, 0, 0.05); color: var(--primary-color); transform: scale(1.1); } .ai-tag-add-category-form { background-color: var(--background-color); padding: 15px; border-radius: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); } .ai-tag-add-category-form h3 { margin-top: 0; margin-bottom: 15px; color: var(--text-color); font-size: 16px; font-weight: 600; } .ai-tag-category-form-group { margin-bottom: 15px; } .ai-tag-category-form-group label { display: block; margin-bottom: 5px; font-weight: 500; color: var(--text-color); font-size: 14px; } .ai-tag-category-form-group input { width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 5px; font-size: 14px; background-color: var(--surface-color); color: var(--text-color); } .ai-tag-category-form-group input:focus { outline: none; border-color: var(--primary-color); } .ai-tag-category-form-color { display: flex; align-items: center; } .ai-tag-category-form-color label { flex: 1; } .ai-tag-category-form-actions { display: flex; gap: 10px; margin-top: 20px; } .ai-tag-edit-category-form { display: none; } `); } /* 2. 主程序入口 */ function main() { console.log("开始初始化主程序"); // 添加样式 addStyles(theme); // 初始化数据库 initDB().then(() => { console.log("数据库初始化成功,开始创建UI"); // 创建UI createUI(); showToast("AI绘画Tag管理器已加载"); }).catch(error => { console.error("初始化失败:", error); alert("AI绘画Tag管理器初始化失败,请刷新页面重试"); }); } // 在文档准备好后运行 if (document.readyState !== 'loading') { console.log("文档已加载,立即初始化"); main(); } else { console.log("文档加载中,等待DOMContentLoaded事件"); document.addEventListener('DOMContentLoaded', main); } /* 3. 数据库操作 */ // 初始化数据库 function initDB() { return new Promise((resolve, reject) => { console.log("开始初始化数据库"); const request = indexedDB.open(DB_NAME, DB_VERSION); request.onupgradeneeded = function(event) { console.log("数据库升级中"); const db = event.target.result; // 标签集合存储 if (!db.objectStoreNames.contains(STORE_NAME)) { const tagStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true }); tagStore.createIndex('category', 'category', { unique: false }); tagStore.createIndex('name', 'name', { unique: false }); console.log("创建标签集合存储"); } // 分类存储 if (!db.objectStoreNames.contains(CATEGORY_STORE)) { const categoryStore = db.createObjectStore(CATEGORY_STORE, { keyPath: 'id' }); categoryStore.createIndex('name', 'name', { unique: true }); categoryStore.createIndex('order', 'order', { unique: false }); // 添加默认分类 DEFAULT_CATEGORIES.forEach(category => { categoryStore.add(category); }); console.log("创建分类存储并添加默认分类"); } console.log("数据库结构已更新"); }; request.onsuccess = function(event) { db = event.target.result; console.log("数据库连接成功"); resolve(db); }; request.onerror = function(event) { console.error("数据库连接失败:", event.target.error); reject(event.target.error); }; }); } // 获取所有分类 function getAllCategories() { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([CATEGORY_STORE], 'readonly'); const store = transaction.objectStore(CATEGORY_STORE); const request = store.getAll(); request.onsuccess = function() { const categories = request.result; // 按顺序排序 categories.sort((a, b) => a.order - b.order); console.log(`获取分类成功,共 ${categories.length} 个`); resolve(categories); }; request.onerror = function(event) { console.error("获取分类失败:", event.target.error); reject(event.target.error); }; }); } // 添加分类 function addCategory(id, name, color, order) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([CATEGORY_STORE], 'readwrite'); const store = transaction.objectStore(CATEGORY_STORE); // 检查ID是否已存在 const getRequest = store.get(id); getRequest.onsuccess = function() { if (getRequest.result) { reject(new Error("分类ID已存在")); return; } // 添加新分类 const request = store.add({ id: id, name: name, color: color, order: order || 999 // 默认排在最后 }); request.onsuccess = function() { console.log("添加分类成功:", id); resolve(); }; request.onerror = function(event) { console.error("添加分类失败:", event.target.error); reject(event.target.error); }; }; getRequest.onerror = function(event) { console.error("检查分类ID失败:", event.target.error); reject(event.target.error); }; }); } // 更新分类 function updateCategory(id, name, color, order) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([CATEGORY_STORE], 'readwrite'); const store = transaction.objectStore(CATEGORY_STORE); const request = store.get(id); request.onsuccess = function() { const category = request.result; if (!category) { reject(new Error("分类不存在")); return; } category.name = name; category.color = color; if (order !== undefined) { category.order = order; } const updateRequest = store.put(category); updateRequest.onsuccess = function() { console.log("更新分类成功:", id); resolve(); }; updateRequest.onerror = function(event) { console.error("更新分类失败:", event.target.error); reject(event.target.error); }; }; request.onerror = function(event) { console.error("获取分类失败:", event.target.error); reject(event.target.error); }; }); } // 删除分类 function deleteCategory(id) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } // 检查是否有标签使用该分类 checkCategoryUsage(id).then(isUsed => { if (isUsed) { reject(new Error("该分类下有标签,无法删除")); return; } const transaction = db.transaction([CATEGORY_STORE], 'readwrite'); const store = transaction.objectStore(CATEGORY_STORE); const request = store.delete(id); request.onsuccess = function() { console.log("删除分类成功:", id); resolve(); }; request.onerror = function(event) { console.error("删除分类失败:", event.target.error); reject(event.target.error); }; }).catch(error => { reject(error); }); }); } // 检查分类是否被使用 function checkCategoryUsage(categoryId) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const index = store.index('category'); const request = index.getAll(categoryId); request.onsuccess = function() { const tags = request.result; resolve(tags.length > 0); }; request.onerror = function(event) { console.error("检查分类使用失败:", event.target.error); reject(event.target.error); }; }); } // 添加标签集合 function addTagCollection(name, tags, category) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.add({ name: name, tags: tags, category: category, createdAt: new Date().toISOString() }); request.onsuccess = function() { console.log("添加标签成功:", name); resolve(request.result); }; request.onerror = function(event) { console.error("添加标签失败:", event.target.error); reject(event.target.error); }; }); } // 批量添加标签集合 function addTagCollections(collections) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); let successCount = 0; let errorCount = 0; collections.forEach(collection => { // 确保必要的字段存在 if (!collection.name || !collection.tags || !collection.category) { errorCount++; return; } const request = store.add({ name: collection.name, tags: collection.tags, category: collection.category, createdAt: collection.createdAt || new Date().toISOString() }); request.onsuccess = function() { successCount++; if (successCount + errorCount === collections.length) { resolve({ success: successCount, error: errorCount }); } }; request.onerror = function() { errorCount++; if (successCount + errorCount === collections.length) { resolve({ success: successCount, error: errorCount }); } }; }); // 如果没有集合要添加 if (collections.length === 0) { resolve({ success: 0, error: 0 }); } }); } // 获取所有标签集合 function getAllTagCollections() { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.getAll(); request.onsuccess = function() { console.log(`获取标签成功,共 ${request.result.length} 个`); resolve(request.result); }; request.onerror = function(event) { console.error("获取标签失败:", event.target.error); reject(event.target.error); }; }); } // 按分类获取标签集合 function getTagCollectionsByCategory(category) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const index = store.index('category'); const request = index.getAll(category); request.onsuccess = function() { console.log(`获取 ${category} 分类标签成功,共 ${request.result.length} 个`); resolve(request.result); }; request.onerror = function(event) { console.error(`获取 ${category} 分类标签失败:`, event.target.error); reject(event.target.error); }; }); } // 搜索标签集合 function searchTagCollections(query) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const store = transaction.objectStore(STORE_NAME); const request = store.getAll(); request.onsuccess = function() { const allCollections = request.result; const lowercaseQuery = query.toLowerCase(); // 过滤匹配查询的集合 const matchedCollections = allCollections.filter(collection => { return collection.name.toLowerCase().includes(lowercaseQuery) || collection.tags.toLowerCase().includes(lowercaseQuery); }); console.log(`搜索结果: ${matchedCollections.length} 个匹配`); resolve(matchedCollections); }; request.onerror = function(event) { console.error("搜索失败:", event.target.error); reject(event.target.error); }; }); } // 更新标签集合 function updateTagCollection(id, name, tags, category) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.get(id); request.onsuccess = function() { const data = request.result; if (!data) { console.error("标签不存在:", id); reject(new Error("标签不存在")); return; } data.name = name; data.tags = tags; data.category = category; data.updatedAt = new Date().toISOString(); const updateRequest = store.put(data); updateRequest.onsuccess = function() { console.log("更新标签成功:", id); resolve(updateRequest.result); }; updateRequest.onerror = function(event) { console.error("更新标签失败:", event.target.error); reject(event.target.error); }; }; request.onerror = function(event) { console.error("获取要更新的标签失败:", event.target.error); reject(event.target.error); }; }); } // 删除标签集合 function deleteTagCollection(id) { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.delete(id); request.onsuccess = function() { console.log("删除标签成功:", id); resolve(); }; request.onerror = function(event) { console.error("删除标签失败:", event.target.error); reject(event.target.error); }; }); } // 清空所有标签 function clearAllTagCollections() { return new Promise((resolve, reject) => { if (!db) { console.error("数据库未初始化"); reject(new Error("数据库未初始化")); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const store = transaction.objectStore(STORE_NAME); const request = store.clear(); request.onsuccess = function() { console.log("清空所有标签成功"); resolve(); }; request.onerror = function(event) { console.error("清空标签失败:", event.target.error); reject(event.target.error); }; }); } /* 4. UI创建与事件绑定 */ // 创建UI元素并绑定事件 function createUI() { console.log("开始创建UI"); // 创建拖动手柄 const handle = document.createElement('div'); handle.id = 'ai-tag-manager-handle'; document.body.appendChild(handle); console.log("拖动手柄已创建"); // 创建抽屉容器 const drawer = document.createElement('div'); drawer.id = 'ai-tag-manager-drawer'; drawer.innerHTML = `
自定义和管理标签分类
${escapeHTML(category.name)}
${category.id}
没有找到标签组合
加载失败,请重试
没有找到匹配的标签组合
尝试其他关键词或创建新组合
搜索失败,请重试