From 559e30ecbf77458e35f267f6d71907701f076ce8 Mon Sep 17 00:00:00 2001 From: 171696257 <1761696257@qq.com> Date: Mon, 23 Jun 2025 15:23:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=BE=88=E5=A4=9A?= =?UTF-8?q?=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-EN.md | 23 +- README.md | 6 +- extensions/Intelligent Layout/LayoutCore.js | 189 +- .../styles/InlineColorPicker.js | 432 ++++ .../Intelligent Layout/styles/UIStyles.js | 11 +- .../Prompt_Manager/PromptManagerCore.js | 134 +- extensions/Prompt_Manager/prompt_api.py | 90 +- .../Prompt_Manager/styles/PromptManagerUI.js | 1914 +++++------------ .../styles/components/DialogComponents.js | 705 ++++++ .../styles/components/MainModal.js | 261 +++ .../styles/components/PromptForm.js | 1076 +++++++++ .../styles/components/PromptList.js | 812 +++++++ .../styles/components/SearchBar.js | 700 ++++++ .../styles/components/TagColorManager.js | 288 +++ .../styles/components/TagManager.js | 1419 ++++++++++++ .../Prompt_Manager/styles/components/index.js | 80 + pyproject.toml | 2 +- 17 files changed, 6669 insertions(+), 1473 deletions(-) create mode 100644 extensions/Intelligent Layout/styles/InlineColorPicker.js create mode 100644 extensions/Prompt_Manager/styles/components/DialogComponents.js create mode 100644 extensions/Prompt_Manager/styles/components/MainModal.js create mode 100644 extensions/Prompt_Manager/styles/components/PromptForm.js create mode 100644 extensions/Prompt_Manager/styles/components/PromptList.js create mode 100644 extensions/Prompt_Manager/styles/components/SearchBar.js create mode 100644 extensions/Prompt_Manager/styles/components/TagColorManager.js create mode 100644 extensions/Prompt_Manager/styles/components/TagManager.js create mode 100644 extensions/Prompt_Manager/styles/components/index.js diff --git a/README-EN.md b/README-EN.md index 26ff25c..766b13c 100644 --- a/README-EN.md +++ b/README-EN.md @@ -16,8 +16,6 @@ Intelligent prompt embedding and management system that provides powerful prompt **Core Features:** - 🔍 **Prompt Search**: Search prompt names and content for quick location and filtering - 📁 **Import/Export**: JSON format prompt backup and sharing - share your prompts with others and import community prompts -- 🏷️ **Tag System**: Organize prompts with up to 4 tags per prompt for easy categorization -- 💾 **Auto Backup**: All prompt operations automatically save to JSON files for data persistence **Use Cases:** - Manage commonly used positive/negative prompt templates @@ -26,6 +24,8 @@ Intelligent prompt embedding and management system that provides powerful prompt - Improve workflow creation efficiency - Efficiently manage LORA model prompts + ![提示词](https://github.com/user-attachments/assets/52bd3fa9-e6e0-486e-a18d-da45b6058e79) +
@@ -105,12 +105,12 @@ High-performance model loading optimizer with multiple optimization modes:
-
- DD Interface Layout +
DD Interface Layout -- Beta version implemented, can randomly or specifically color node titles, and quickly organize messy workflows. -- Beautified UI, added theme system, each theme has two different styles to switch between, supports custom themes. -- Added modular presets, you can preset your own layouts and apply them with one click. +- Can randomly or specifically set node title colors, quickly organize messy nodes +- Beautify UI, add theme system, each theme has two different styles to switch between, click the coin animation to switch + +![面板](https://github.com/user-attachments/assets/ef4d9bb9-57a7-445d-8515-3bf07f1d48bc)
@@ -121,6 +121,8 @@ Provides various cool animation effects for ComfyUI node connections, supporting - Supports "Flow", "Wave", "Rhythm", "Pulse" and other animation styles - Animation parameters are customizable (line width, speed, effects, etc.) +![连线](https://github.com/user-attachments/assets/56621f61-fe41-48fc-8871-471f5d411a17) +
## Installation @@ -144,6 +146,10 @@ This extension now supports ComfyUI's native internationalization (i18n) system, 3. Currently only supports node translation, frontend functionality translation is not yet supported ## Version History +- v2.6.0 (2025-06-23) +- Complete reconstruction of the prompt management panel with many new features +- Improved interface layout panel custom color functionality, now with a dedicated color preset panel + - v2.5.0 (2025-06-20) - 🔄 **Prompt Manager New Features**: - Added intelligent backup system - all prompt operations automatically save to extensions\Prompt_Manager\prompts.json @@ -265,6 +271,3 @@ This is a growing collection of nodes, primarily for sharing practical ComfyUI e Your support is the motivation for the author to continue coding! ╭(●`∀´●)╯ ![Payment QR Code](https://github.com/user-attachments/assets/77c99c94-3854-4c12-81cf-09c9f76099ac) - -Your support is the motivation for the author to continue development! ╭(●`∀´●)╯ -![QR Code](https://github.com/user-attachments/assets/77c99c94-3854-4c12-81cf-09c9f76099ac) diff --git a/README.md b/README.md index 05da06e..a4b353b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@
DD 界面布局 -- 已实现Alpha版本,可以随机或指定节点标题的颜色,可以对杂乱节点做快速整理 +- 可以随机或指定节点标题的颜色,可以对杂乱节点做快速整理 - 美化UI,添加主题系统,每一种主题有两种不同风格可以切换,点击硬币动画即可切换 ![面板](https://github.com/user-attachments/assets/ef4d9bb9-57a7-445d-8515-3bf07f1d48bc) @@ -149,6 +149,10 @@ 3. 暂时只支持节点翻译,暂不支持前端功能翻译切换 ## 版本历史 +- v2.6.0 (2025-06-23) +- 完善提示词管理面板做了完全重构,多了很多新特性 +- 完善界面布局面板的自定义颜色功能,现在有了单独的颜色预设面板 + - v2.5.0 (2025-06-20) - 🔄 **提示词管理器新功能**: - 新增智能备份功能,所有提示词操作自动保存到插件的extensions\Prompt_Manager\prompts.json文件中 diff --git a/extensions/Intelligent Layout/LayoutCore.js b/extensions/Intelligent Layout/LayoutCore.js index dcf6f12..5d7c0f9 100644 --- a/extensions/Intelligent Layout/LayoutCore.js +++ b/extensions/Intelligent Layout/LayoutCore.js @@ -36,6 +36,9 @@ import { alignNodes } from './styles/UIUtils.js'; +// 导入内嵌颜色选择器组件 +import { InlineColorPicker } from './styles/InlineColorPicker.js'; + // 跟踪鼠标位置 let mousePosition = { x: 0, y: 0 }; document.addEventListener('mousemove', (e) => { @@ -49,8 +52,8 @@ export class LayoutPanel { this.container = null; this.buttonsContainer = null; this.colorMode = '完全随机'; // 默认为完全随机模式 - this.currentTheme = null; - this.coinElement = null; + this.currentTheme = null; this.coinElement = null; + this.inlineColorPicker = null; // 内嵌颜色选择器实例 } setEnabled() { @@ -118,9 +121,11 @@ export class LayoutPanel { this.visible = true; } - - hide() { - if (!this.container) return; + hide() { + if (!this.container) return; // 隐藏颜色选择器(如果存在且可见) + if (this.inlineColorPicker && typeof this.inlineColorPicker.isVisible === 'function' && this.inlineColorPicker.isVisible()) { + this.inlineColorPicker.hide(); + } // 只淡出,不修改 transform this.container.style.opacity = '0'; @@ -215,12 +220,11 @@ export class LayoutPanel { e.stopPropagation(); this._setRandomNodeColor(); }); - - // 创建自定义颜色按钮 + // 创建自定义颜色按钮 const customColorBtn = createButton('自定义颜色', 'normal'); customColorBtn.addEventListener('click', (e) => { e.stopPropagation(); - this._openCustomColorPicker(); + this._toggleInlineColorPicker(); }); // 创建节点同步按钮 @@ -260,16 +264,45 @@ export class LayoutPanel { this.colorMode = e.target.value; }); modeContainer.appendChild(modeSelect); - - // 添加按钮到容器 + // 添加按钮到容器 this.buttonsContainer.appendChild(randomColorBtn); this.buttonsContainer.appendChild(customColorBtn); this.buttonsContainer.appendChild(syncBtn); - // 添加按钮容器和模式选择器到主容器 - this.container.appendChild(this.buttonsContainer); - this.container.appendChild(modeContainer); - this.container.appendChild(syncModeContainer); + // 创建控制区域容器(包含按钮和下拉框) + this.controlsContainer = document.createElement('div'); + this.controlsContainer.className = 'layout-controls-container'; + this.controlsContainer.appendChild(this.buttonsContainer); + this.controlsContainer.appendChild(modeContainer); + this.controlsContainer.appendChild(syncModeContainer); // 创建并添加内嵌颜色选择器 + this.inlineColorPicker = new InlineColorPicker({ + title: '选择节点颜色', + defaultColor: '#3355aa', + onColorSelect: (color) => { + // 获取选中的节点和组 + const app = getComfyUIApp(); + if (app) { + const selectedNodes = getSelectedNodes(app); + const selectedGroups = getSelectedGroups(app); + this._applyColorToSelectedNodes(color, selectedNodes, selectedGroups, app); + // 应用颜色后恢复控制区域 + this._showControls(); + } + }, + onCancel: () => { + // 取消时恢复控制区域 + this._showControls(); + } + }); + + const colorPickerElement = this.inlineColorPicker.createInlineColorPicker(); + + // 将控制区域和颜色选择器都添加到主容器 + this.container.appendChild(this.controlsContainer); + this.container.appendChild(colorPickerElement); + + // 确保初始状态:显示控制区域,隐藏颜色选择器 + this._showControls(); // 添加面板调整大小时重新调整按钮文字大小的功能 const resizeObserver = new ResizeObserver(() => { @@ -398,9 +431,8 @@ export class LayoutPanel { showNotification(`设置随机颜色失败: ${error.message}`); } } - - // 打开自定义颜色选择器 - _openCustomColorPicker() { + // 打开自定义颜色选择器 // 切换内嵌颜色选择器显示/隐藏 + _toggleInlineColorPicker() { try { // 获取ComfyUI应用实例 const app = getComfyUIApp(); @@ -416,48 +448,64 @@ export class LayoutPanel { if (selectedNodes.length === 0 && selectedGroups.length === 0) { showNotification("请先选择要应用颜色的节点或组"); return; + } // 获取默认颜色 + const defaultColor = selectedNodes.length > 0 ? + (selectedNodes[0].color || '#3355aa') : + (selectedGroups.length > 0 ? (selectedGroups[0].color || '#3355aa') : '#3355aa'); + + // 切换颜色选择器显示状态 + if (this.inlineColorPicker && typeof this.inlineColorPicker.isVisible === 'function') { + if (this.inlineColorPicker.isVisible()) { + this._showControls(); + } else { + this._showColorPicker(defaultColor); + } + } else { + showNotification("颜色选择器未正确初始化"); } - - // 创建颜色选择器 - setTimeout(() => { - const defaultColor = selectedNodes.length > 0 ? - (selectedNodes[0].color || '#3355aa') : - (selectedGroups.length > 0 ? (selectedGroups[0].color || '#3355aa') : '#3355aa'); - - const colorInput = document.createElement('input'); - colorInput.type = 'color'; - colorInput.value = defaultColor.replace(/rgba?\(.*\)/, '#000000'); - colorInput.style.position = 'absolute'; - colorInput.style.visibility = 'hidden'; - document.body.appendChild(colorInput); - - // 处理颜色变化 - colorInput.addEventListener('input', (e) => { - const color = e.target.value; - - // 应用颜色到节点和组 - selectedNodes.forEach(node => { - node.color = color; - }); - - selectedGroups.forEach(group => { - group.color = color; - }); - - app.graph.setDirtyCanvas(true, true); - }); - - colorInput.addEventListener('change', (e) => { - showNotification("已应用自定义颜色", "info"); - document.body.removeChild(colorInput); - }); - - // 模拟点击打开颜色选择器 - colorInput.click(); - }, 100); } catch (error) { - console.error("打开颜色选择器失败:", error); - showNotification(`打开颜色选择器失败: ${error.message}`); + console.error("切换颜色选择器失败:", error); + showNotification(`切换颜色选择器失败: ${error.message}`); + } + } + + // 显示控制区域,隐藏颜色选择器 + _showControls() { + if (this.controlsContainer) { + this.controlsContainer.style.display = 'block'; + } + if (this.inlineColorPicker && typeof this.inlineColorPicker.hide === 'function') { + this.inlineColorPicker.hide(); + } + } + + // 显示颜色选择器,隐藏控制区域 + _showColorPicker(defaultColor) { + if (this.controlsContainer) { + this.controlsContainer.style.display = 'none'; + } + if (this.inlineColorPicker && typeof this.inlineColorPicker.show === 'function') { + this.inlineColorPicker.show(defaultColor); + } + } + + // 应用颜色到选中的节点和组 + _applyColorToSelectedNodes(color, selectedNodes, selectedGroups, app) { + try { + // 应用颜色到节点和组 + selectedNodes.forEach(node => { + node.color = color; + }); + + selectedGroups.forEach(group => { + group.color = color; + }); + + app.graph.setDirtyCanvas(true, true); + showNotification("已应用自定义颜色", "info"); + } catch (error) { + console.error("应用颜色失败:", error); + showNotification(`应用颜色失败: ${error.message}`); } } @@ -501,6 +549,33 @@ export class LayoutPanel { showNotification('节点同步失败: ' + e.message); } } + + // 清理资源 + destroy() { + // 隐藏面板 + if (this.visible) { + this.hide(); + } + // 销毁颜色选择器 + if (this.inlineColorPicker) { + this.inlineColorPicker.destroy(); + this.inlineColorPicker = null; + } + + // 移除面板元素 + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + + // 清理样式 + removeStyles('layout-panel-styles'); + + // 重置状态 + this.container = null; + this.buttonsContainer = null; + this.coinElement = null; + this.visible = false; + } } export const DEFAULT_CONFIG = { diff --git a/extensions/Intelligent Layout/styles/InlineColorPicker.js b/extensions/Intelligent Layout/styles/InlineColorPicker.js new file mode 100644 index 0000000..cdff8eb --- /dev/null +++ b/extensions/Intelligent Layout/styles/InlineColorPicker.js @@ -0,0 +1,432 @@ +// 智能布局内嵌颜色选择器组件 - 修正版 +export class InlineColorPicker { + constructor(options = {}) { + this.onColorSelect = options.onColorSelect || null; + this.onCancel = options.onCancel || null; + this.defaultColor = options.defaultColor || '#3355aa'; + this.title = options.title || '选择颜色'; + + this.container = null; + this.selectedColor = this.defaultColor; + this._isVisible = false; // 使用 _isVisible 避免方法名冲突 + + // 自动创建样式 + this.addStyles(); + } + + // 创建内嵌式颜色选择器 DOM 元素 + createInlineColorPicker() { + this.container = document.createElement('div'); + this.container.className = 'layout-inline-color-picker'; + this.container.style.display = 'none'; + this.container.innerHTML = ` +
+
+ 🎨 + ${this.title} +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+ + +
+
+ `; + + // 添加事件监听器 + this.addEventListeners(); + + // 设置默认选中颜色 + this.setSelectedColor(this.defaultColor); + + return this.container; + } + + addEventListeners() { + if (!this.container) return; + + // 预设颜色点击事件 + const colorOptions = this.container.querySelectorAll('.color-option'); + colorOptions.forEach(option => { + option.addEventListener('click', () => { + const color = option.dataset.color; + this.setSelectedColor(color); + }); + }); + + // 自定义颜色输入事件 + const colorInput = this.container.querySelector('.color-input'); + const hexInput = this.container.querySelector('.color-hex-input'); + + if (colorInput) { + colorInput.addEventListener('input', (e) => { + const color = e.target.value; + this.setSelectedColor(color); + if (hexInput) hexInput.value = color; + }); + } + + if (hexInput) { + hexInput.addEventListener('input', (e) => { + const color = e.target.value; + if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color)) { + this.setSelectedColor(color); + if (colorInput) colorInput.value = color; + } + }); + } + + // 按钮事件 + const applyBtn = this.container.querySelector('.apply-btn'); + const cancelBtn = this.container.querySelector('.cancel-btn'); + + if (applyBtn) { + applyBtn.addEventListener('click', () => { + this.handleConfirm(); + }); + } if (cancelBtn) { + cancelBtn.addEventListener('click', () => { + this.hide(); + // 在取消按钮点击时调用onCancel回调 + if (this.onCancel) { + this.onCancel(); + } + }); + } + } + + setSelectedColor(color) { + this.selectedColor = color; + + // 更新预览 + const preview = this.container?.querySelector('.color-preview'); + if (preview) { + preview.style.background = color; + } + + // 更新输入框 + const colorInput = this.container?.querySelector('.color-input'); + const hexInput = this.container?.querySelector('.color-hex-input'); + if (colorInput) colorInput.value = color; + if (hexInput) hexInput.value = color; + + // 更新选中状态 + const colorOptions = this.container?.querySelectorAll('.color-option'); + colorOptions?.forEach(option => { + option.classList.remove('selected'); + if (option.dataset.color === color) { + option.classList.add('selected'); + } + }); + } + + show(defaultColor) { + if (!this.container) return; + + if (defaultColor) { + this.setSelectedColor(defaultColor); + } + + this.container.style.display = 'block'; + this._isVisible = true; + + // 添加显示动画 + requestAnimationFrame(() => { + this.container.style.opacity = '1'; + this.container.style.transform = 'translateY(0)'; + }); + } hide() { + if (!this.container) return; + + // 添加隐藏动画 + this.container.style.opacity = '0'; + this.container.style.transform = 'translateY(-10px)'; + + setTimeout(() => { + this.container.style.display = 'none'; + this._isVisible = false; + }, 200); + + // 注意:不在hide方法中调用onCancel,避免循环调用 + // onCancel应该只在用户点击取消按钮时调用 + } + + handleConfirm() { + if (this.onColorSelect) { + this.onColorSelect(this.selectedColor); + } + this.hide(); + } + + isVisible() { + return this._isVisible; + } + + destroy() { + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + } + + addStyles() { + // 检查是否已经添加了样式 + if (document.querySelector('#inline-color-picker-styles')) { + return; + } + + const style = document.createElement('style'); + style.id = 'inline-color-picker-styles'; + style.textContent = ` /* ========== 智能布局内嵌颜色选择器样式 ========== */ + .layout-inline-color-picker { + width: 100%; + margin-top: 0px; /* 移除上边距,因为要替换显示 */ + background: linear-gradient(145deg, rgba(26, 26, 46, 0.9), rgba(22, 33, 62, 0.9)); + border: 1px solid rgba(176, 141, 225, 0.3); + border-radius: 10px; + box-shadow: + 0 8px 24px rgba(0, 0, 0, 0.4), + 0 0 15px rgba(138, 43, 226, 0.15); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + opacity: 0; + transform: translateY(-10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + } + + .layout-inline-color-picker::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(176, 141, 225, 0.7), transparent); + } + + /* ========== 头部样式 ========== */ + .inline-color-picker-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px 8px; + border-bottom: 1px solid rgba(176, 141, 225, 0.2); + background: linear-gradient(135deg, rgba(176, 141, 225, 0.1), rgba(138, 43, 226, 0.05)); + border-radius: 10px 10px 0 0; + } + + .picker-title { + display: flex; + align-items: center; + gap: 6px; + color: #e1e1e1; + font-size: 14px; + font-weight: 600; + letter-spacing: 0.3px; + } + + .title-icon { + font-size: 14px; + opacity: 0.9; + } + + /* ========== 内容区域 ========== */ + .inline-color-picker-content { + padding: 12px 16px 16px; + } + + .section-label { + color: #ccc; + font-size: 12px; + font-weight: 500; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 4px; + opacity: 0.9; + } + + .section-label::before { + content: '●'; + color: rgba(176, 141, 225, 0.7); + font-size: 6px; + } + + /* ========== 预设颜色区域 ========== */ + .preset-colors-section { + margin-bottom: 15px; + } + + .preset-colors-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 6px; + padding: 10px; + background: linear-gradient(145deg, rgba(0, 0, 0, 0.2), rgba(42, 30, 74, 0.15)); + border: 1px solid rgba(176, 141, 225, 0.15); + border-radius: 6px; + } + + .color-option { + width: 24px; + height: 24px; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid transparent; + position: relative; + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + } + + .color-option:hover { + transform: scale(1.1); + box-shadow: + 0 3px 12px rgba(0, 0, 0, 0.4), + 0 0 8px rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); + } + + .color-option.selected { + border-color: rgba(176, 141, 225, 0.8); + box-shadow: + 0 0 0 2px rgba(138, 43, 226, 0.4), + 0 3px 12px rgba(0, 0, 0, 0.4), + 0 0 15px rgba(138, 43, 226, 0.3); + transform: scale(1.05); + } + + .color-option.selected::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #ffffff; + font-size: 10px; + font-weight: bold; + text-shadow: + 0 0 3px rgba(0, 0, 0, 0.8), + 0 1px 2px rgba(0, 0, 0, 0.6); + } + + /* ========== 自定义颜色区域 ========== */ + .custom-color-section { + margin-bottom: 15px; + } + + .custom-color-row { + display: flex; + align-items: center; + gap: 8px; + padding: 10px; + background: linear-gradient(145deg, rgba(0, 0, 0, 0.2), rgba(42, 30, 74, 0.15)); + border: 1px solid rgba(176, 141, 225, 0.15); + border-radius: 6px; + } + + .color-input { + width: 40px; + height: 28px; + border: none; + border-radius: 4px; + cursor: pointer; + background: none; + outline: none; + } + + .color-preview { + width: 28px; + height: 28px; + border-radius: 4px; + border: 2px solid rgba(176, 141, 225, 0.3); + background: #3355aa; + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + } + + .color-hex-input { + flex: 1; + padding: 6px 8px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(176, 141, 225, 0.2); + border-radius: 4px; + color: #e1e1e1; + font-size: 12px; + outline: none; + transition: all 0.2s ease; + } + + .color-hex-input:focus { + border-color: rgba(176, 141, 225, 0.5); + box-shadow: 0 0 8px rgba(138, 43, 226, 0.2); + } + + /* ========== 按钮区域 ========== */ + .picker-buttons { + display: flex; + gap: 8px; + justify-content: flex-end; + } + + .cancel-btn, .apply-btn { + padding: 6px 16px; + border: 1px solid rgba(176, 141, 225, 0.3); + border-radius: 4px; + background: linear-gradient(145deg, rgba(26, 26, 46, 0.8), rgba(22, 33, 62, 0.8)); + color: #e1e1e1; + font-size: 12px; + cursor: pointer; + transition: all 0.2s ease; + outline: none; + } + + .cancel-btn:hover { + background: linear-gradient(145deg, rgba(120, 120, 120, 0.2), rgba(80, 80, 80, 0.2)); + border-color: rgba(176, 141, 225, 0.5); + box-shadow: 0 0 8px rgba(138, 43, 226, 0.2); + } + + .apply-btn { + background: linear-gradient(145deg, rgba(176, 141, 225, 0.3), rgba(138, 43, 226, 0.3)); + border-color: rgba(176, 141, 225, 0.5); + } + + .apply-btn:hover { + background: linear-gradient(145deg, rgba(176, 141, 225, 0.4), rgba(138, 43, 226, 0.4)); + border-color: rgba(176, 141, 225, 0.7); + box-shadow: 0 0 12px rgba(138, 43, 226, 0.3); + } + `; + + document.head.appendChild(style); + } +} diff --git a/extensions/Intelligent Layout/styles/UIStyles.js b/extensions/Intelligent Layout/styles/UIStyles.js index 616c891..072a5e7 100644 --- a/extensions/Intelligent Layout/styles/UIStyles.js +++ b/extensions/Intelligent Layout/styles/UIStyles.js @@ -162,6 +162,12 @@ export const layoutPanelStyles = ` transition: opacity 0.3s; border-radius: 16px; } + + /* 控制区域容器样式 */ + .layout-controls-container { + display: block; + width: 100%; + } `; // ========================================================== @@ -179,9 +185,8 @@ export function createPanelElement(mousePosition) { container.style.transform = 'none'; container.style.display = 'none'; container.style.pointerEvents = 'none'; - container.style.opacity = '0'; - container.style.padding = '30px'; // 增加内边距适应大尺寸硬币 - container.style.width = '360px'; // 设置基础宽度 + container.style.opacity = '0'; container.style.padding = '30px'; // 增加内边距适应大尺寸硬币 + container.style.width = '360px'; // 恢复原始宽度 container.style.boxSizing = 'border-box'; // 确保padding不增加总宽度 // 添加拖动区域 - 改为在面板边缘创建多个拖动区域 diff --git a/extensions/Prompt_Manager/PromptManagerCore.js b/extensions/Prompt_Manager/PromptManagerCore.js index 79e5863..531a1cb 100644 --- a/extensions/Prompt_Manager/PromptManagerCore.js +++ b/extensions/Prompt_Manager/PromptManagerCore.js @@ -1,5 +1,6 @@ // 提示词管理器核心模块,只包含业务逻辑和数据管理 import { PromptManagerUI } from './styles/PromptManagerUI.js'; +import { globalTagColorManager } from './styles/components/TagColorManager.js'; export class PromptManager { constructor() { @@ -7,95 +8,71 @@ export class PromptManager { this.loadPrompts(); } async loadPrompts() { try { - // 首先尝试从后端API加载最新数据 + // 从后端API加载最新数据 const response = await fetch('/dd_nodes/load_prompts'); if (response.ok) { const result = await response.json(); - if (result.success && result.prompts && result.prompts.length > 0) { - this.prompts = result.prompts; + if (result.success && result.data && result.data.prompts) { + this.prompts = result.data.prompts; // 确保所有提示词都有标签属性(向后兼容) this.prompts.forEach(prompt => { if (!prompt.tags) { prompt.tags = []; } }); - // 同步到本地存储作为缓存 - localStorage.setItem('comfyui_dd_prompts', JSON.stringify(this.prompts)); + + // 加载标签颜色数据(如果存在) + if (result.data.tags && typeof result.data.tags === 'object') { + console.log('从后端加载标签颜色数据:', result.data.tags); + // 恢复标签颜色到全局标签颜色管理器 + Object.entries(result.data.tags).forEach(([tagName, color]) => { + globalTagColorManager.setTagColor(tagName, color); + }); + } + console.log(`从后端API加载了 ${this.prompts.length} 个提示词`); return; } } - // 如果API失败,尝试从本地存储加载 - const localData = localStorage.getItem('comfyui_dd_prompts'); - if (localData) { - this.prompts = JSON.parse(localData); - // 确保所有提示词都有标签属性(向后兼容) - this.prompts.forEach(prompt => { - if (!prompt.tags) { - prompt.tags = []; - } - }); - console.log(`从本地存储加载了 ${this.prompts.length} 个提示词`); - // 尝试同步到后端 - this.syncToBackend(); - return; - } - - // 如果都没有数据,使用空列表 + // 如果API失败,使用空列表 this.prompts = []; console.log('初始化空的提示词列表'); } catch (error) { - console.log('加载提示词失败,尝试从本地存储加载:', error); - // 错误处理:从本地存储加载 - try { - const localData = localStorage.getItem('comfyui_dd_prompts'); - if (localData) { - this.prompts = JSON.parse(localData); - this.prompts.forEach(prompt => { - if (!prompt.tags) { - prompt.tags = []; - } - }); - } else { - this.prompts = []; - } - } catch (localError) { - console.error('本地存储也加载失败:', localError); - this.prompts = []; - } + console.error('加载提示词失败:', error); + this.prompts = []; } - } savePrompts() { + }savePrompts() { try { - // 1. 保存到本地存储(作为缓存) - localStorage.setItem('comfyui_dd_prompts', JSON.stringify(this.prompts)); - - // 2. 自动同步到后端API(主要存储) + // 直接同步到后端API this.syncToBackend(); - - console.log('提示词已保存并自动同步到后端'); + console.log('提示词已自动同步到后端'); } catch (error) { console.error('保存提示词失败:', error); } - } - - // 同步数据到后端API + } // 同步数据到后端API async syncToBackend() { try { + // 获取标签数据 - 直接从标签颜色管理器获取最新数据 + const tagsData = Object.fromEntries(globalTagColorManager.colorMap); + + console.log('同步标签数据到后端:', tagsData); + const response = await fetch('/dd_nodes/save_prompts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - prompts: this.prompts + prompts: this.prompts, + tags: tagsData // 包含标签数据 }) }); if (response.ok) { const result = await response.json(); if (result.success) { - console.log(`成功同步 ${result.count} 个提示词到后端`); + console.log(`成功同步 ${result.count} 个提示词和 ${Object.keys(tagsData).length} 个标签到后端`); } else { console.error('后端同步失败:', result.error); } @@ -103,9 +80,9 @@ export class PromptManager { console.error('后端同步请求失败:', response.status); } } catch (error) { - console.warn('后端同步失败,仅保存到本地存储:', error); + console.error('后端同步失败:', error); } - } // 已弃用:自动同步功能已转移到后端API + }// 已弃用:自动同步功能已转移到后端API // 保留此方法仅为向后兼容 autoSyncToJsonFile() { // 现在通过syncToBackend()方法同步到后端 @@ -320,13 +297,17 @@ export class PromptManager { return searchResults; } // 导出提示词到 JSON 格式 exportPrompts() { + // 获取标签颜色数据 + const tagsData = Object.fromEntries(globalTagColorManager.colorMap); + // 使用当前最新的提示词数据 const promptsData = { version: "2.4.0", exportTime: new Date().toISOString(), description: "ComfyUI-DD-Nodes 提示词管理器导出的提示词数据", totalCount: this.prompts.length, - prompts: this.prompts + prompts: this.prompts, + tags: tagsData // 添加标签颜色数据 }; const dataStr = JSON.stringify(promptsData, null, 2); @@ -341,18 +322,26 @@ export class PromptManager { link.click(); URL.revokeObjectURL(url); - } // 从 JSON 文件导入提示词(兼容新旧格式) + }// 从 JSON 文件导入提示词(兼容新旧格式) importPrompts(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = (e) => { - try { + reader.onload = (e) => { try { const imported = JSON.parse(e.target.result); let promptsToImport = []; // 检查是否是新格式(包含metadata) if (imported.prompts && Array.isArray(imported.prompts)) { promptsToImport = imported.prompts; + + // 导入标签颜色数据(如果存在) + if (imported.tags && typeof imported.tags === 'object') { + console.log('导入标签颜色数据:', imported.tags); + // 合并标签颜色到全局标签颜色管理器 + Object.entries(imported.tags).forEach(([tagName, color]) => { + globalTagColorManager.setTagColor(tagName, color); + }); + } } // 兼容旧格式(直接是数组) else if (Array.isArray(imported)) { @@ -507,13 +496,33 @@ export class PromptEmbedder { } return false; - } applyPromptToWidget(prompt, widget) { + } applyPromptToWidget(prompt, widget, insertMode = null) { if (!widget) return; - // 检查是否需要处理现有内容 const currentValue = widget.value || ''; const hasContent = currentValue.trim().length > 0; + // 如果指定了插入模式,直接使用 + if (insertMode) { + let newValue; + if (insertMode === 'insert') { + // 插入到选中位置或开头 + newValue = this.insertPromptAtCursor(widget, prompt.content); + } else if (insertMode === 'append') { + // 追加到内容末尾 + newValue = currentValue + (currentValue ? ', ' : '') + prompt.content; + } else if (insertMode === 'replace') { + // 替换全部内容 + newValue = prompt.content; + } + + if (newValue !== undefined) { + this.setWidgetValue(widget, newValue); + } + return; + } + + // 如果没有指定插入模式且有内容,显示对话框 if (hasContent) { // 显示自定义确认对话框 this.ui.showInsertDialog( @@ -524,6 +533,9 @@ export class PromptEmbedder { if (action === 'insert') { // 插入到选中位置或开头 newValue = this.insertPromptAtCursor(widget, prompt.content); + } else if (action === 'append') { + // 追加到内容末尾 + newValue = currentValue + (currentValue ? ', ' : '') + prompt.content; } else if (action === 'replace') { // 替换全部内容 newValue = prompt.content; @@ -536,7 +548,7 @@ export class PromptEmbedder { } else { this.setWidgetValue(widget, prompt.content); } - } insertPromptAtCursor(widget, promptText) { + }insertPromptAtCursor(widget, promptText) { const currentValue = widget.value || ''; let cursorPosition = 0; diff --git a/extensions/Prompt_Manager/prompt_api.py b/extensions/Prompt_Manager/prompt_api.py index 4f23076..28aeb31 100644 --- a/extensions/Prompt_Manager/prompt_api.py +++ b/extensions/Prompt_Manager/prompt_api.py @@ -50,13 +50,17 @@ class PromptManagerAPI: data = await request.json() prompts = data.get('prompts', []) + # 获取标签数据(如果有的话) + tags = data.get('tags', {}) + # 准备要保存的数据 prompts_data = { "version": "2.4.0", "exportTime": datetime.now().isoformat(), "description": "ComfyUI-DD-Nodes 提示词管理器数据文件 - 自动同步", "totalCount": len(prompts), - "prompts": prompts + "prompts": prompts, + "tags": tags # 添加标签数据 } # 写入JSON文件 @@ -89,11 +93,13 @@ class PromptManagerAPI: if prompts_data: prompts = prompts_data.get('prompts', []) + tags = prompts_data.get('tags', {}) # 获取标签数据 logger.info(f"成功加载 {len(prompts)} 个提示词从JSON文件") return web.json_response({ "success": True, "data": prompts_data, "prompts": prompts, + "tags": tags, # 返回标签数据 "count": len(prompts) }) else: @@ -101,6 +107,7 @@ class PromptManagerAPI: "success": True, "data": None, "prompts": [], + "tags": {}, # 空标签数据 "count": 0 }) @@ -117,6 +124,7 @@ class PromptManagerAPI: try: data = await request.json() prompts = data.get('prompts', []) + tags = data.get('tags', {}) operation = data.get('operation', 'full_sync') # full_sync, add, update, delete if operation == 'full_sync': @@ -126,10 +134,11 @@ class PromptManagerAPI: "exportTime": datetime.now().isoformat(), "description": "ComfyUI-DD-Nodes 提示词管理器数据文件 - 自动同步", "totalCount": len(prompts), - "prompts": prompts + "prompts": prompts, + "tags": tags } success = await self.write_prompts_file(prompts_data) - message = f"全量同步 {len(prompts)} 个提示词" + message = f"全量同步 {len(prompts)} 个提示词和 {len(tags)} 个标签" else: # 增量更新(预留接口) @@ -160,6 +169,81 @@ class PromptManagerAPI: "success": False, "error": str(e) }, status=500) + + @PromptServer.instance.routes.post("/dd_nodes/save_tags") + async def save_tags(request): + """保存标签数据到JSON文件""" + try: + data = await request.json() + tags = data.get('tags', {}) + + # 读取现有的数据 + current_data = await self.read_prompts_file() + if current_data: + # 更新标签数据,保留现有提示词 + current_data['tags'] = tags + current_data['exportTime'] = datetime.now().isoformat() + else: + # 创建新的数据结构 + current_data = { + "version": "2.4.0", + "exportTime": datetime.now().isoformat(), + "description": "ComfyUI-DD-Nodes 提示词管理器数据文件 - 标签更新", + "totalCount": 0, + "prompts": [], + "tags": tags + } + + # 写入JSON文件 + success = await self.write_prompts_file(current_data) + + if success: + logger.info(f"成功保存 {len(tags)} 个标签到JSON文件") + return web.json_response({ + "success": True, + "message": f"成功保存 {len(tags)} 个标签", + "count": len(tags) + }) + else: + return web.json_response({ + "success": False, + "error": "写入文件失败" + }, status=500) + + except Exception as e: + logger.error(f"保存标签失败: {e}") + return web.json_response({ + "success": False, + "error": str(e) + }, status=500) + + @PromptServer.instance.routes.get("/dd_nodes/load_tags") + async def load_tags(request): + """从JSON文件加载标签数据""" + try: + prompts_data = await self.read_prompts_file() + + if prompts_data: + tags = prompts_data.get('tags', {}) + logger.info(f"成功加载 {len(tags)} 个标签从JSON文件") + return web.json_response({ + "success": True, + "tags": tags, + "count": len(tags) + }) + else: + return web.json_response({ + "success": True, + "tags": {}, + "count": 0 + }) + + except Exception as e: + logger.error(f"加载标签失败: {e}") + return web.json_response({ + "success": False, + "error": str(e) + }, status=500) async def read_prompts_file(self): """读取提示词JSON文件""" diff --git a/extensions/Prompt_Manager/styles/PromptManagerUI.js b/extensions/Prompt_Manager/styles/PromptManagerUI.js index 1061cb2..180b413 100644 --- a/extensions/Prompt_Manager/styles/PromptManagerUI.js +++ b/extensions/Prompt_Manager/styles/PromptManagerUI.js @@ -1,1432 +1,672 @@ -// 提示词管理器 UI 模块,包含所有界面相关的逻辑和样式 +// 提示词管理器 UI 主控制器 - 重构后的版本 +// 负责组件组合和逻辑调度,不包含具体的UI实现 + +import { MainModal } from './components/MainModal.js'; +import { SearchBar } from './components/SearchBar.js'; +import { PromptList } from './components/PromptList.js'; +import { PromptForm } from './components/PromptForm.js'; +import { DialogComponents } from './components/DialogComponents.js'; +import { TagManager } from './components/TagManager.js'; +import { globalTagColorManager } from './components/TagColorManager.js'; export class PromptManagerUI { constructor(embedder) { this.embedder = embedder; this.promptManager = embedder.promptManager; this.node = embedder.node; + // 组件实例 + this.mainModal = null; + this.searchBar = null; + this.promptList = null; + this.promptForm = null; + this.dialogComponents = null; + this.tagManager = null; + + // 状态管理 this.isVisible = false; this.editingPrompt = null; this.currentSearchKeyword = ''; - this.createModal(); + + this.initializeComponents(); + this.setupEventHandlers(); } - createModal() { - // 创建模态框HTML结构 - this.modal = document.createElement('div'); - this.modal.className = 'prompt-embedder-modal'; - this.modal.innerHTML = ` -
-
-
-

嵌入提示词管理

- -
-
-
-
-

提示词列表

-
- - - -
-
-
-
- -
🔍
-
-
-
-
- -
+ initializeComponents() { + // 初始化主模态框 + this.mainModal = new MainModal('提示词管理'); + + // 初始化对话框组件 + this.dialogComponents = new DialogComponents(); + // 初始化列表组件 + this.promptList = new PromptList({ + onEdit: (index, prompt) => this.handleEditPrompt(index, prompt), + onDelete: (index, prompt) => this.handleDeletePrompt(index, prompt), + onApply: (index, prompt) => this.handleApplyPrompt(index, prompt), + onTagClick: (tagName) => this.handleTagClick(tagName) + }); // 初始化搜索栏 (在列表组件之后初始化) + this.searchBar = new SearchBar( + // 兼容旧的搜索回调 + (keyword) => { + this.currentSearchKeyword = keyword; + + // 设置原始数据,然后应用搜索关键词 + this.promptList.setPrompts(this.promptManager.getPrompts()); + const searchResultCount = this.promptList.filterPrompts(keyword); + + return searchResultCount; + }, + // 新的标签筛选回调 + (keyword, selectedTags) => { + this.currentSearchKeyword = keyword; + + // 设置原始数据,然后应用搜索关键词和标签筛选 + this.promptList.setPrompts(this.promptManager.getPrompts()); + const searchResultCount = this.promptList.filterPrompts(keyword, selectedTags); + + return searchResultCount; + } + ); // 初始化表单组件 + this.promptForm = new PromptForm({ + onSubmit: (formData, isEditing, editingPrompt) => this.handleFormSubmit(formData, isEditing, editingPrompt), + onCancel: () => this.handleFormCancel(), + getPrompts: () => this.promptManager.getPrompts() // 新增:提供获取提示词数据的回调 + }); + + // 初始化标签管理组件 + this.tagManager = new TagManager({ + onClose: () => { + // 标签管理关闭后刷新标签筛选区域 + if (this.searchBar && this.searchBar.refreshTags) { + this.searchBar.refreshTags(); + } + }, onTagColorChange: () => { + // 标签颜色变更后立即刷新所有组件的标签颜色和标签列表 + this.refreshAllTagColors(); + // 同时刷新搜索栏的标签列表 + if (this.searchBar && this.searchBar.refreshTags) { + this.searchBar.refreshTags(); + } + }, + promptManager: this.promptManager + }); + + this.assembleUI(); + } + + assembleUI() { + const contentContainer = this.mainModal.getContentContainer(); + + // 创建左右分栏布局 + contentContainer.innerHTML = ` +
+
+ +
+
+
`; - - // 添加样式 - this.addStyles(); - // 绑定事件 - this.bindEvents(); + const mainSection = contentContainer.querySelector('.prompt-manager-main'); + const formSection = contentContainer.querySelector('.prompt-manager-form'); + // 插入组件 + mainSection.appendChild(this.promptList.getElement()); + mainSection.insertBefore(this.searchBar.getElement(), this.promptList.getElement()); + formSection.appendChild(this.promptForm.getElement()); + formSection.appendChild(this.tagManager.getElement()); - document.body.appendChild(this.modal); - } - - addStyles() { - if (document.getElementById('prompt-embedder-styles')) return; + // 添加布局样式 + this.addLayoutStyles(); + } setupEventHandlers() { + // 绑定头部操作按钮事件 + const headerActions = this.promptList.getHeaderActions(); - const styles = document.createElement('style'); - styles.id = 'prompt-embedder-styles'; - styles.textContent = ` - .prompt-embedder-modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 10000; - } - - .prompt-embedder-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.7); - display: flex; - justify-content: center; - align-items: center; - } - - .prompt-embedder-container { - background: #1a1a1a; - border-radius: 8px; - border: 1px solid #3d3d3d; - width: 90%; - max-width: 800px; - max-height: 90%; - overflow: hidden; - color: #ffffff; - } - - .prompt-embedder-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 15px 20px; - border-bottom: 1px solid #3d3d3d; - background: #262626; - } - - .prompt-embedder-header h3 { - margin: 0; - color: #ffffff; - } - - .close-btn { - background: none; - border: none; - color: #ffffff; - font-size: 24px; - cursor: pointer; - padding: 0; - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - } - - .close-btn:hover { - background: #ff5733; - border-radius: 50%; - } - - .prompt-embedder-content { - padding: 20px; - max-height: 600px; - overflow-y: auto; - } - - .prompt-list-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 15px; - } - - .prompt-list-header h4 { - margin: 0; - color: #ffffff; - } - - .header-actions { - display: flex; - gap: 8px; - } - - .add-prompt-btn, .import-btn, .export-btn { - background: #2a82e4; - border: none; - color: white; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - font-size: 12px; - transition: background 0.2s; - } - - .add-prompt-btn:hover { - background: #1f6bb8; - } - - .import-btn { - background: #43cf7c; - } - - .import-btn:hover { - background: #38b569; - } - - .export-btn { - background: #ffc300; - color: #000; - } - - .export-btn:hover { - background: #e6af00; - } - - .prompt-list { - min-height: 200px; - } - - .search-section { - margin-bottom: 15px; - } - - .search-container { - position: relative; - display: flex; - align-items: center; - } - - .search-input { - width: 100%; - padding: 10px 40px 10px 12px; - background: #262626; - border: 1px solid #3d3d3d; - border-radius: 6px; - color: #ffffff; - font-size: 14px; - outline: none; - transition: border-color 0.2s; - } - - .search-input:focus { - border-color: #2a82e4; - } - - .search-input::placeholder { - color: #888; - } - - .search-icon { - position: absolute; - right: 12px; - color: #888; - pointer-events: none; - } - - .empty-state { - text-align: center; - padding: 40px 20px; - color: #888; - } - - .empty-state-icon { - font-size: 48px; - margin-bottom: 15px; - opacity: 0.5; - } - - .prompt-item { - background: #262626; - border: 1px solid #3d3d3d; - border-radius: 6px; - padding: 15px; - margin-bottom: 10px; - transition: all 0.2s; - } - - .prompt-item:hover { - border-color: #2a82e4; - } - - .prompt-item-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 8px; - } - - .prompt-item-title-section { - flex: 1; - min-width: 0; - } - - .prompt-item-name { - font-weight: bold; - color: #ffffff; - font-size: 16px; - margin-bottom: 4px; - } - - .prompt-item-tags { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-top: 4px; - } - - .tag { - display: inline-block; - background: #2a82e4; - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 11px; - font-weight: 500; - line-height: 1.2; - white-space: nowrap; - max-width: 100px; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; - transition: all 0.2s ease; - user-select: none; - } - - .tag:hover { - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(42, 130, 228, 0.3); - filter: brightness(1.1); - } - - .tag:nth-child(even) { - background: #43cf7c; - } - - .tag:nth-child(even):hover { - box-shadow: 0 2px 8px rgba(67, 207, 124, 0.3); - } - - .tag:nth-child(3n) { - background: #ff8c42; - } - - .tag:nth-child(3n):hover { - box-shadow: 0 2px 8px rgba(255, 140, 66, 0.3); - } - - .tag:nth-child(4n) { - background: #9b59b6; - } - - .tag:nth-child(4n):hover { - box-shadow: 0 2px 8px rgba(155, 89, 182, 0.3); - } - - .prompt-item-actions { - display: flex; - gap: 8px; - } - - .edit-btn, .apply-btn, .delete-btn { - background: #383838; - border: none; - color: white; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - transition: background 0.2s; - } - - .edit-btn:hover { - background: #ffc300; - color: #000; - } - - .apply-btn:hover { - background: #43cf7c; - } - - .delete-btn { - background: #8b3a3a; - } - - .delete-btn:hover { - background: #ff5733; - } - - .prompt-item-content { - color: #c4c4c4; - line-height: 1.4; - background: #1a1a1a; - padding: 10px; - border-radius: 4px; - border: 1px solid #3d3d3d; - white-space: pre-wrap; - } - - .prompt-form-section { - border-top: 1px solid #3d3d3d; - padding-top: 20px; - margin-top: 20px; - } - - .form-title { - margin: 0 0 15px 0; - color: #ffffff; - } - - .form-group { - margin-bottom: 15px; - } - - .form-group label { - display: block; - margin-bottom: 5px; - color: #ffffff; - font-weight: bold; - } - - .form-group input, - .form-group textarea { - width: 100%; - padding: 10px; - background: #262626; - border: 1px solid #3d3d3d; - border-radius: 4px; - color: #ffffff; - font-size: 14px; - resize: vertical; - } - - .form-group input:focus, - .form-group textarea:focus { - outline: none; - border-color: #2a82e4; - } - - .form-actions { - display: flex; - gap: 10px; - justify-content: flex-end; - } - - .save-btn { - background: #43cf7c; - border: none; - color: white; - padding: 10px 20px; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - } - - .save-btn:hover { - background: #38b569; - } - - .cancel-btn { - background: #ff5733; - border: none; - color: white; - padding: 10px 20px; - border-radius: 4px; - cursor: pointer; - font-weight: bold; - } - - .cancel-btn:hover { - background: #e04929; - } - - .search-highlight { - background-color: #ffc300; - color: #000; - padding: 1px 2px; - border-radius: 2px; - font-weight: bold; - } - `; - document.head.appendChild(styles); - } - - bindEvents() { - // 关闭按钮 - this.modal.querySelector('.close-btn').addEventListener('click', () => { - this.hide(); - }); - - // 点击遮罩层关闭 - this.modal.querySelector('.prompt-embedder-overlay').addEventListener('click', (e) => { - if (e.target === e.currentTarget) { - this.hide(); - } - }); - - // 添加提示词按钮 - this.modal.querySelector('.add-prompt-btn').addEventListener('click', () => { + headerActions.addBtn.addEventListener('click', () => { this.showForm(); }); - - // 导出按钮 - this.modal.querySelector('.export-btn').addEventListener('click', () => { - this.promptManager.exportPrompts(); - }); - - // 导入按钮 - this.modal.querySelector('.import-btn').addEventListener('click', () => { - this.showImportDialog(); - }); - - // 表单提交 - this.modal.querySelector('.prompt-form').addEventListener('submit', (e) => { - e.preventDefault(); - this.handleFormSubmit(); - }); - - // 取消按钮 - this.modal.querySelector('.cancel-btn').addEventListener('click', () => { - this.hideForm(); - }); - - // 搜索框事件监听 - const searchInput = this.modal.querySelector('.search-input'); - // 输入事件 - 实时搜索 - searchInput.addEventListener('input', (e) => { - this.currentSearchKeyword = e.target.value; - this.renderPromptList(); + headerActions.importBtn.addEventListener('click', () => { + this.handleImport(); }); - - // 清空搜索框快捷键 (Escape) - searchInput.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - e.target.value = ''; - this.currentSearchKeyword = ''; - this.renderPromptList(); - } + + headerActions.exportBtn.addEventListener('click', () => { + this.handleExport(); }); - } - + + headerActions.tagManagerBtn.addEventListener('click', () => { + this.handleTagManager(); + }); + } // 显示管理器 show() { this.isVisible = true; - this.modal.style.display = 'block'; + this.mainModal.show(); + + // 确保布局容器初始状态正确(没有展开类) + const layoutContainer = this.mainModal.getContentContainer().querySelector('.prompt-manager-layout'); + if (layoutContainer) { + layoutContainer.classList.remove('form-expanded'); + } // 重置搜索状态 this.currentSearchKeyword = ''; - const searchInput = this.modal.querySelector('.search-input'); - if (searchInput) { - searchInput.value = ''; - } + this.searchBar.setKeyword(''); - this.renderPromptList(); - } - - hide() { - this.isVisible = false; - this.modal.style.display = 'none'; - this.hideForm(); - } - + // 刷新数据 + this.refreshPromptList(); + } // 显示表单 showForm(prompt = null) { this.editingPrompt = prompt; - const formSection = this.modal.querySelector('.prompt-form-section'); - const form = this.modal.querySelector('.prompt-form'); - const title = this.modal.querySelector('.form-title'); + + // 隐藏标签管理器(如果正在显示) + if (this.tagManager && this.tagManager.isVisible()) { + this.tagManager.hide(); + } if (prompt) { - title.textContent = '编辑提示词'; - form.name.value = prompt.name; - form.content.value = prompt.content; - form.tags.value = (prompt.tags || []).join(', '); + this.promptForm.show('edit', prompt); } else { - title.textContent = '添加提示词'; - form.reset(); + this.promptForm.show('add'); } - - formSection.style.display = 'block'; - form.name.focus(); - } - + } // 隐藏表单 hideForm() { this.editingPrompt = null; - this.modal.querySelector('.prompt-form-section').style.display = 'none'; - this.modal.querySelector('.prompt-form').reset(); + this.promptForm.hide(); } - handleFormSubmit() { - const form = this.modal.querySelector('.prompt-form'); - const name = form.name.value.trim(); - const content = form.content.value.trim(); - const tagsInput = form.tags.value.trim(); - - if (!name || !content) { - alert('请填写完整的提示词名称和内容'); - return; - } - - // 处理标签输入 - let tags = []; - if (tagsInput) { - tags = tagsInput.split(',') - .map(tag => tag.trim()) - .filter(tag => tag.length > 0) - .slice(0, 4); // 限制最多4个标签 - } - - if (this.editingPrompt) { - this.promptManager.updatePrompt(this.editingPrompt.id, name, content, tags); - } else { - this.promptManager.addPrompt(name, content, tags); - } - - this.hideForm(); - this.renderPromptList(); + // 处理编辑提示词 + async handleEditPrompt(index, prompt) { + this.showForm(prompt); } - renderPromptList() { - const listContainer = this.modal.querySelector('.prompt-list'); - - // 使用搜索功能获取提示词列表 - const prompts = this.currentSearchKeyword - ? this.promptManager.searchPrompts(this.currentSearchKeyword) - : this.promptManager.getPrompts(); - - // 如果搜索无结果,显示相应提示 - if (this.currentSearchKeyword && prompts.length === 0) { - const isMultiTagSearch = this.currentSearchKeyword.includes(','); - const helpText = isMultiTagSearch - ? '尝试减少标签数量或使用不同的标签组合' - : '尝试使用其他关键词、标签名称或用逗号分隔多个标签搜索'; + // 处理删除提示词 + async handleDeletePrompt(index, prompt) { + const confirmed = await this.dialogComponents.showDeleteConfirm(prompt.name); + if (confirmed) { + try { + this.promptManager.deletePrompt(prompt.id); + this.refreshPromptList(); - listContainer.innerHTML = ` -
-
🔍
-
未找到匹配"${this.escapeHtml(this.currentSearchKeyword)}"的提示词
-
${helpText}
-
- `; - return; - } - - // 如果没有提示词,显示空状态 - if (prompts.length === 0) { - listContainer.innerHTML = ` -
-
📝
-
暂无提示词,点击上方"+ 添加提示词"按钮来添加第一个提示词
-
- `; - return; - } - - // 渲染提示词列表,如果是搜索结果,高亮显示匹配的关键词 - listContainer.innerHTML = prompts.map(prompt => { - let displayName = this.escapeHtml(prompt.name); - let displayContent = this.escapeHtml(prompt.content); - let displayTags = (prompt.tags || []).map(tag => this.escapeHtml(tag)); - - // 如果有搜索关键词,高亮显示匹配的部分 - if (this.currentSearchKeyword) { - const keyword = this.escapeHtml(this.currentSearchKeyword); - const highlightClass = 'search-highlight'; - - // 处理多标签搜索的高亮 - const isMultiTagSearch = keyword.includes(','); - if (isMultiTagSearch) { - const searchTags = keyword.split(',').map(tag => tag.trim()).filter(tag => tag); - - // 高亮匹配的标签 - displayTags = displayTags.map(tag => { - const tagLower = tag.toLowerCase(); - const matchedSearchTag = searchTags.find(searchTag => - tagLower.includes(searchTag.toLowerCase()) - ); - if (matchedSearchTag) { - const tagRegex = new RegExp(`(${matchedSearchTag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); - return tag.replace(tagRegex, `$1`); - } - return tag; - }); - } else { - // 单关键词搜索的高亮 - const nameRegex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); - displayName = displayName.replace(nameRegex, `$1`); - - const contentRegex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); - displayContent = displayContent.replace(contentRegex, `$1`); - - // 高亮标签中的匹配文本 - displayTags = displayTags.map(tag => { - return tag.replace(nameRegex, `$1`); - }); - } + // 删除成功,不显示弹窗,直接完成操作 + } catch (error) { + console.error('删除提示词失败:', error); + await this.dialogComponents.showErrorDialog( + '删除失败', + error.message || '删除提示词时发生错误' + ); } - - // 生成标签HTML - const tagsHtml = displayTags.length > 0 - ? `
${displayTags.map(tag => `${tag}`).join('')}
` - : ''; - - return ` -
-
-
-
${displayName}
- ${tagsHtml} -
-
- - - -
-
-
${displayContent}
-
- `; - }).join(''); - - // 移除之前的事件监听器(如果存在) - const existingListener = this._listClickHandler; - if (existingListener) { - listContainer.removeEventListener('click', existingListener); } - - // 创建新的事件处理器 - this._listClickHandler = (e) => { - const action = e.target.getAttribute('data-action'); - if (!action) return; // 如果没有action属性,则不处理 - - const promptItem = e.target.closest('.prompt-item'); - if (!promptItem) return; // 如果找不到对应的提示词项,则不处理 - - const promptId = parseInt(promptItem.getAttribute('data-id')); - const prompt = prompts.find(p => p.id === promptId); + } // 处理应用提示词 + async handleApplyPrompt(index, prompt) { + try { + // 获取所有文本组件 + const textWidgets = this.embedder.getAllTextWidgets(); - if (!prompt) { - console.error('找不到对应的提示词:', promptId); + if (textWidgets.length === 0) { + await this.dialogComponents.showErrorDialog( + '应用失败', + '未找到可用的文本输入框' + ); return; } - // 防止事件冒泡 - e.stopPropagation(); - e.preventDefault(); + let targetWidget = null; - try { - if (action === 'edit') { - this.showForm(prompt); - } else if (action === 'apply') { - this.embedder.applyPrompt(prompt); - } else if (action === 'delete') { - this.deletePrompt(prompt); + // 如果有多个文本组件,显示选择器 + if (textWidgets.length > 1) { + targetWidget = await this.dialogComponents.showWidgetSelector( + textWidgets, + (widget) => this.embedder.getWidgetDisplayName(widget) + ); + + if (!targetWidget) { + return; // 用户取消选择 } - } catch (error) { - console.error('操作失败:', error); - alert('操作失败: ' + error.message); + } else { + targetWidget = textWidgets[0]; } - }; - // 绑定新的事件监听器 - listContainer.addEventListener('click', this._listClickHandler); - - // 添加标签点击事件处理 - const tagElements = listContainer.querySelectorAll('.tag'); - tagElements.forEach(tagElement => { - tagElement.addEventListener('click', (e) => { - e.stopPropagation(); - e.preventDefault(); - - // 获取标签文本(去除HTML高亮标记) - const tagText = tagElement.textContent.trim(); - - // 在搜索框中设置标签文本并执行搜索 - const searchInput = this.modal.querySelector('.search-input'); - searchInput.value = tagText; - this.currentSearchKeyword = tagText; - this.renderPromptList(); - - // 聚焦到搜索框 - searchInput.focus(); - }); + // 检查是否有现有内容 + const currentValue = targetWidget.value || ''; + const hasContent = currentValue.trim().length > 0; - // 添加标签悬停提示 - tagElement.title = `点击搜索标签: ${tagElement.textContent.trim()}`; - }); + let insertMode = 'replace'; // 默认模式 + + if (hasContent) { + // 显示插入模式选择对话框 + insertMode = await this.dialogComponents.showInsertModeDialog( + '选择插入模式', + '检测到输入框中已有内容,请选择插入模式:' + ); + + if (!insertMode) { + return; // 用户取消或关闭对话框 + } + } // 应用提示词 + this.embedder.applyPromptToWidget(prompt, targetWidget, insertMode); + + // 隐藏管理器 + this.hide(); + + // 不再显示成功提示,直接完成操作 + + } catch (error) { + console.error('应用提示词失败:', error); + await this.dialogComponents.showErrorDialog( + '应用失败', + error.message || '应用提示词时发生错误' + ); + } } - deletePrompt(prompt) { - // 显示确认删除对话框 - this.showDeleteConfirmDialog(prompt.name, () => { - this.promptManager.deletePrompt(prompt.id); - this.renderPromptList(); - }); + // 标签筛选和搜索组合逻辑 + filterPromptsWithTagsAndSearch(keyword, selectedTags) { + let allPrompts = this.promptManager.getPrompts(); + + // 如果有搜索关键词,先进行搜索筛选 + if (keyword && keyword.trim()) { + allPrompts = this.promptManager.searchPrompts(keyword.trim()); + } + + // 如果有选中的标签,进行标签筛选 + if (selectedTags && selectedTags.length > 0) { + allPrompts = allPrompts.filter(prompt => { + if (!prompt.tags || !Array.isArray(prompt.tags)) { + return false; + } + + // 检查提示词是否包含所有选中的标签(AND逻辑) + return selectedTags.every(selectedTag => + prompt.tags.some(tag => tag && tag.trim() === selectedTag) + ); + }); + } + + return allPrompts; } - showImportDialog() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.json'; - input.addEventListener('change', async (e) => { - const file = e.target.files[0]; + // 处理标签点击(从提示词卡片中点击标签) + handleTagClick(tagName) { + // 将标签添加到搜索栏的筛选中 + this.searchBar.selectedTags.add(tagName); + this.searchBar.renderTags(); + this.searchBar.updateActiveFiltersDisplay(); + this.searchBar.handleFilter(); + } + + // 处理表单提交 + async handleFormSubmit(formData, isEditing, editingPrompt) { + try { + const { name, content, tags } = formData; if (isEditing && editingPrompt) { + // 编辑模式 + this.promptManager.updatePrompt(editingPrompt.id, name, content, tags); + + // 更新成功,不显示弹窗,直接完成操作 + } else { + // 添加模式 + this.promptManager.addPrompt(name, content, tags); + + // 添加成功,不显示弹窗,直接完成操作 + } + + this.hideForm(); + this.refreshPromptList(); + + } catch (error) { + console.error('保存提示词失败:', error); + await this.dialogComponents.showErrorDialog( + '保存失败', + error.message || '保存提示词时发生错误' + ); + } + } + + // 处理表单取消 + handleFormCancel() { + this.hideForm(); + } + + // 处理导入 + async handleImport() { + try { + const file = await this.dialogComponents.showFileImportDialog('.json'); + if (file) { - try { - const count = await this.promptManager.importPrompts(file); - alert(`成功导入 ${count} 个提示词!`); - this.renderPromptList(); - } catch (error) { - alert(`导入失败: ${error}`); - } + const count = await this.promptManager.importPrompts(file); + this.refreshPromptList(); + await this.dialogComponents.showSuccessDialog( + '导入成功', + `成功导入 ${count} 个提示词!`, + 1500 // 缩短显示时间到1.5秒 + ); } - }); - input.click(); + } catch (error) { + console.error('导入失败:', error); + await this.dialogComponents.showErrorDialog( + '导入失败', + error.message || '导入文件时发生错误' + ); + } } - showTextWidgetSelector(prompt) { - const textWidgets = this.embedder.getAllTextWidgets(); - - if (textWidgets.length <= 1) { - return false; // 不需要显示选择器 + // 处理导出 + async handleExport() { + try { + const success = this.promptManager.exportPrompts(); + + if (success) { await this.dialogComponents.showSuccessDialog( + '导出成功', + '提示词数据已导出到文件!', + 1500 // 缩短显示时间到1.5秒 + ); + } + } catch (error) { + console.error('导出失败:', error); + await this.dialogComponents.showErrorDialog( + '导出失败', + error.message || '导出文件时发生错误' + ); + } } // 处理标签管理 + handleTagManager() { + // 如果标签管理器已经显示,则隐藏它 + if (this.tagManager.isVisible()) { + this.tagManager.hide(); + return; } - // 创建选择器模态框 - const selectorModal = document.createElement('div'); - selectorModal.className = 'widget-selector-modal'; - selectorModal.innerHTML = ` -
-
-
-

选择文本输入框

- -
-
-

该节点有多个文本输入框,请选择要应用提示词的输入框:

-
- ${textWidgets.map((widget, index) => ` - - `).join('')} -
-
-
-
- `; - - // 添加选择器样式 - this.addWidgetSelectorStyles(); - - // 绑定事件 - selectorModal.querySelector('.close-btn').addEventListener('click', () => { - document.body.removeChild(selectorModal); - }); - - selectorModal.querySelector('.widget-selector-overlay').addEventListener('click', (e) => { - if (e.target === e.currentTarget) { - document.body.removeChild(selectorModal); - } - }); - - selectorModal.addEventListener('click', (e) => { - if (e.target.classList.contains('widget-option')) { - const index = parseInt(e.target.getAttribute('data-index')); - const selectedWidget = textWidgets[index]; - this.embedder.applyPromptToWidget(prompt, selectedWidget); - document.body.removeChild(selectorModal); - } - }); - - document.body.appendChild(selectorModal); - return true; - } - - showInsertDialog(title, message, callback) { - const insertModal = document.createElement('div'); - insertModal.className = 'confirm-dialog-modal'; - insertModal.innerHTML = ` -
-
-
-

${title}

- -
-
-

${message}

-
-
-
插入 - 在光标位置插入
-
在当前光标位置或文本开头插入提示词
-
-
-
替换 - 替换全部内容
-
删除原有内容,使用新提示词替换
-
-
-
-
-
- `; - - // 添加确认对话框样式 - this.addConfirmDialogStyles(); - - // 绑定事件 - insertModal.querySelector('.dialog-close-btn').addEventListener('click', () => { - document.body.removeChild(insertModal); - }); - - insertModal.querySelectorAll('.option-card').forEach(card => { - card.addEventListener('click', () => { - const action = card.getAttribute('data-action'); - callback(action); - document.body.removeChild(insertModal); - }); - }); - - insertModal.querySelector('.confirm-dialog-overlay').addEventListener('click', (e) => { - if (e.target === e.currentTarget) { - document.body.removeChild(insertModal); - } - }); - - document.body.appendChild(insertModal); - } - - showConfirmDialog(title, message, callback) { - const confirmModal = document.createElement('div'); - confirmModal.className = 'confirm-dialog-modal'; - confirmModal.innerHTML = ` -
-
-
-

${title}

- -
-
-

${message}

-
-
-
确定 - 追加到现有内容后面
-
保留原有内容,将新提示词添加到后面
-
-
-
取消 - 替换全部内容
-
删除原有内容,使用新提示词替换
-
-
-
-
-
- `; - - // 添加确认对话框样式 - this.addConfirmDialogStyles(); - - // 绑定事件 - confirmModal.querySelector('.dialog-close-btn').addEventListener('click', () => { - document.body.removeChild(confirmModal); - }); - - confirmModal.querySelectorAll('.option-card').forEach(card => { - card.addEventListener('click', () => { - const action = card.getAttribute('data-action'); - callback(action === 'append'); - document.body.removeChild(confirmModal); - }); - }); - - confirmModal.querySelector('.confirm-dialog-overlay').addEventListener('click', (e) => { - if (e.target === e.currentTarget) { - document.body.removeChild(confirmModal); - } - }); - - document.body.appendChild(confirmModal); - } - - showDeleteConfirmDialog(promptName, callback) { - // 移除已存在的删除确认对话框 - const existingModal = document.querySelector('.delete-confirm-modal'); - if (existingModal) { - existingModal.remove(); + // 隐藏表单(如果正在显示) + if (this.promptForm.isVisible()) { + this.promptForm.hide(); } - - const confirmModal = document.createElement('div'); - confirmModal.className = 'delete-confirm-modal'; - confirmModal.innerHTML = ` -
-
-
-

确认删除

- -
-
-

确定要删除提示词 "${this.escapeHtml(promptName)}" 吗?

-

此操作不可撤销!

-
- - -
-
-
-
- `; - - this.addDeleteConfirmStyles(); - - // 安全的事件绑定 - const closeBtn = confirmModal.querySelector('.delete-close-btn'); - const confirmBtn = confirmModal.querySelector('.delete-confirm-btn'); - const cancelBtn = confirmModal.querySelector('.delete-cancel-btn'); - const overlay = confirmModal.querySelector('.delete-confirm-overlay'); - - const removeModal = () => { - if (confirmModal.parentNode) { - confirmModal.parentNode.removeChild(confirmModal); - } - }; - - closeBtn.addEventListener('click', removeModal); - cancelBtn.addEventListener('click', removeModal); + this.editingPrompt = null; - confirmBtn.addEventListener('click', () => { - try { - callback(); - } catch (error) { - console.error('删除操作失败:', error); - } finally { - removeModal(); - } - }); - - overlay.addEventListener('click', (e) => { - if (e.target === overlay) { - removeModal(); - } - }); - - document.body.appendChild(confirmModal); + // 显示标签管理器 + this.tagManager.show(); } - escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - - addWidgetSelectorStyles() { - if (document.getElementById('widget-selector-styles')) return; - - const styles = document.createElement('style'); - styles.id = 'widget-selector-styles'; - styles.textContent = ` - .widget-selector-modal { - display: block; - position: fixed; - z-index: 10001; - left: 0; - top: 0; - width: 100%; - height: 100%; - } - - .widget-selector-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); + // 添加布局样式 + addLayoutStyles() { + const style = document.createElement('style'); + style.textContent = ` .prompt-manager-layout { display: flex; - justify-content: center; - align-items: center; - } - - .widget-selector-container { - background: #2a2a2a; - border-radius: 8px; - min-width: 400px; - max-width: 500px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); - } - - .widget-selector-header { - background: #1a1a1a; - padding: 15px 20px; - border-radius: 8px 8px 0 0; - display: flex; - justify-content: space-between; - align-items: center; - } - - .widget-selector-content { - padding: 20px; - } - - .widget-list { + height: 100%; + max-height: calc(100vh - 160px); + background: #1e1e1e; + border-radius: 0 0 16px 16px; + overflow: hidden; + position: relative; + } .prompt-manager-main { + width: 100%; display: flex; flex-direction: column; - gap: 10px; - margin-top: 15px; - } - - .widget-option { - background: #3a3a3a; - border: 1px solid #555; - color: white; - padding: 12px 16px; - border-radius: 4px; - cursor: pointer; - text-align: left; - transition: background 0.2s; - } - - .widget-option:hover { - background: #4a4a4a; - } - `; - document.head.appendChild(styles); - } - - addConfirmDialogStyles() { - if (document.getElementById('confirm-dialog-styles')) return; - - const styles = document.createElement('style'); - styles.id = 'confirm-dialog-styles'; - styles.textContent = ` - .confirm-dialog-modal { - display: block; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 10002; + overflow: hidden; + background: #1e1e1e; + position: relative; + transition: width 0.3s ease-out; + will-change: width; + transform: translateZ(0); + backface-visibility: hidden; } - .confirm-dialog-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - display: flex; - justify-content: center; - align-items: center; - } - - .confirm-dialog-container { - background: linear-gradient(145deg, #2a2a2a, #1f1f1f); - border: 1px solid #404040; - border-radius: 12px; - min-width: 400px; - max-width: 500px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); - animation: confirmDialogFadeIn 0.3s ease-out; - } - - @keyframes confirmDialogFadeIn { - from { - opacity: 0; - transform: scale(0.9) translateY(-20px); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } - } - - .confirm-dialog-header { - background: linear-gradient(145deg, #333333, #2a2a2a); - padding: 16px 20px; - border-radius: 12px 12px 0 0; - border-bottom: 1px solid #404040; - display: flex; - justify-content: space-between; - align-items: center; - } - - .confirm-dialog-header h4 { - margin: 0; - color: #ffffff; - font-size: 16px; - font-weight: 600; - } - - .dialog-close-btn { - background: none; - border: none; - color: #999999; - font-size: 20px; - cursor: pointer; - padding: 4px; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all 0.2s ease; - } - - .dialog-close-btn:hover { - background: #ff5733; - color: #ffffff; - transform: scale(1.1); - } - - .confirm-dialog-content { - padding: 20px; - color: #e0e0e0; - } - - .confirm-dialog-content p { - margin: 0 0 15px 0; - font-size: 14px; - line-height: 1.5; - } - - .confirm-dialog-options { - display: flex; - flex-direction: column; - gap: 12px; - margin-top: 15px; - } - - .option-card { - background: linear-gradient(145deg, #2a2a2a, #1f1f1f); - border: 2px solid #404040; - border-radius: 8px; - padding: 16px; - cursor: pointer; - transition: all 0.3s ease; + /* 当表单显示时,左侧区域收缩到固定宽度 */ + .prompt-manager-layout.form-expanded .prompt-manager-main { + width: 800px; + } .prompt-manager-form { + width: 0; + background: linear-gradient(145deg, #2a2a2a, #232323); + border-left: 1px solid rgba(255, 255, 255, 0.1); position: relative; overflow: hidden; + opacity: 0; + transition: width 0.3s ease-out, + opacity 0.3s ease-out; + will-change: width, opacity; + transform: translateZ(0); + backface-visibility: hidden; } - .option-card::before { + /* 当表单显示时,右侧区域展开到400px宽度 */ + .prompt-manager-layout.form-expanded .prompt-manager-form { + width: 400px; + opacity: 1; + } /* 硬件加速优化 */ + .prompt-manager-layout, + .prompt-manager-main, + .prompt-manager-form { + backface-visibility: hidden; + perspective: 1000px; + transform: translateZ(0); + } + + .prompt-manager-form::before { content: ''; position: absolute; top: 0; - left: -100%; - width: 100%; + left: 0; + width: 1px; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); - transition: left 0.5s ease; + background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.2), transparent); + z-index: 2; } - .option-card:hover { - border-color: #2a82e4; - background: linear-gradient(145deg, #333333, #2a2a2a); - transform: translateY(-2px); - box-shadow: 0 8px 16px rgba(42, 130, 228, 0.2); + .prompt-manager-form .prompt-form-section { + border-top: none; + border-left: none; + height: 100%; + background: transparent; } - .option-card:hover::before { - left: 100%; + /* 光滑滚动 */ + .prompt-manager-main, + .prompt-manager-form { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.3) transparent; } - .option-card:active { - transform: translateY(0); - box-shadow: 0 4px 8px rgba(42, 130, 228, 0.3); + .prompt-manager-main::-webkit-scrollbar, + .prompt-manager-form::-webkit-scrollbar { + width: 6px; + height: 6px; } - .option-title { - font-size: 14px; - font-weight: 600; - color: #ffffff; - margin-bottom: 6px; + .prompt-manager-main::-webkit-scrollbar-track, + .prompt-manager-form::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 3px; } - .option-description { - font-size: 12px; - color: #c0c0c0; - line-height: 1.4; + .prompt-manager-main::-webkit-scrollbar-thumb, + .prompt-manager-form::-webkit-scrollbar-thumb { + background: linear-gradient(145deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.1)); + border-radius: 3px; + border: 1px solid rgba(255, 255, 255, 0.05); } - .option-card[data-action="append"] .option-title { - color: #2ea043; - } - - .option-card[data-action="replace"] .option-title { - color: #f85149; + .prompt-manager-main::-webkit-scrollbar-thumb:hover, + .prompt-manager-form::-webkit-scrollbar-thumb:hover { + background: linear-gradient(145deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.2)); } `; - document.head.appendChild(styles); - } - - addDeleteConfirmStyles() { - if (document.getElementById('delete-confirm-styles')) return; - const styles = document.createElement('style'); - styles.id = 'delete-confirm-styles'; - styles.textContent = ` - .delete-confirm-modal { - display: block; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 10003; - } - - .delete-confirm-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - display: flex; - justify-content: center; - align-items: center; - } - - .delete-confirm-container { - background: linear-gradient(145deg, #2a2a2a, #1f1f1f); - border: 1px solid #404040; - border-radius: 12px; - min-width: 400px; - max-width: 500px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); - animation: deleteConfirmFadeIn 0.3s ease-out; - } - - @keyframes deleteConfirmFadeIn { - from { - opacity: 0; - transform: scale(0.9) translateY(-20px); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } - } - - .delete-confirm-header { - background: linear-gradient(145deg, #333333, #2a2a2a); - padding: 16px 20px; - border-radius: 12px 12px 0 0; - border-bottom: 1px solid #404040; - display: flex; - justify-content: space-between; - align-items: center; - } - - .delete-confirm-header h4 { - margin: 0; - color: #ffffff; - font-size: 16px; - font-weight: 600; - } - - .delete-close-btn { - background: none; - border: none; - color: #999999; - font-size: 20px; - cursor: pointer; - padding: 4px; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all 0.2s ease; - } - - .delete-close-btn:hover { - background: #ff5733; - color: #ffffff; - transform: scale(1.1); - } - - .delete-confirm-content { - padding: 20px; - color: #e0e0e0; - } - - .delete-confirm-content p { - margin: 0 0 15px 0; - font-size: 14px; - line-height: 1.5; - } - - .warning-text { - color: #ff9999; - font-size: 12px; - margin-top: 10px; - margin-bottom: 0; - } - - .delete-confirm-actions { - display: flex; - gap: 12px; - justify-content: flex-end; - margin-top: 20px; - } - - .delete-confirm-btn { - background: linear-gradient(145deg, #dc3545, #c82333); - border: none; - color: white; - padding: 10px 20px; - border-radius: 6px; - cursor: pointer; - font-weight: 600; - font-size: 14px; - transition: all 0.2s ease; - } - - .delete-confirm-btn:hover { - background: linear-gradient(145deg, #e74c3c, #dc3545); - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(220, 53, 69, 0.4); - } - - .delete-cancel-btn { - background: linear-gradient(145deg, #6c757d, #5a6268); - border: none; - color: white; - padding: 10px 20px; - border-radius: 6px; - cursor: pointer; - font-weight: 600; - font-size: 14px; - transition: all 0.2s ease; - } - - .delete-cancel-btn:hover { - background: linear-gradient(145deg, #7c8590, #6c757d); - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(108, 117, 125, 0.4); - } - `; - document.head.appendChild(styles); + if (!document.head.querySelector('style[data-component="prompt-manager-layout"]')) { + style.setAttribute('data-component', 'prompt-manager-layout'); + document.head.appendChild(style); + } } -} + + // 销毁组件 + destroy() { + // 销毁所有子组件 + if (this.mainModal) { + this.mainModal.destroy(); + } + + if (this.searchBar) { + this.searchBar.destroy(); + } + + if (this.promptList) { + this.promptList.destroy(); + } + + if (this.promptForm) { + this.promptForm.destroy(); + } + if (this.dialogComponents) { + this.dialogComponents.destroy(); + } + + if (this.tagManager) { + this.tagManager.destroy(); + } + + // 移除布局样式 + const style = document.head.querySelector('style[data-component="prompt-manager-layout"]'); + if (style) { + style.remove(); + } + + // 重置状态 + this.isVisible = false; + this.editingPrompt = null; + this.currentSearchKeyword = ''; + } + + // 获取当前状态信息 + getState() { + return { + isVisible: this.isVisible, + currentSearchKeyword: this.currentSearchKeyword, + editingPrompt: this.editingPrompt, + totalPrompts: this.promptManager.getPrompts().length + }; + } + + // 设置搜索关键词(外部调用) + setSearchKeyword(keyword) { + this.searchBar.setKeyword(keyword); + } + + // 获取当前搜索关键词 + getSearchKeyword() { + return this.currentSearchKeyword; + } // 隐藏管理器 + hide() { + this.isVisible = false; + this.hideForm(); // 先隐藏表单 + + // 隐藏标签管理器(如果正在显示) + if (this.tagManager && this.tagManager.isVisible()) { + this.tagManager.hide(); + } + + this.mainModal.hide(); + } + + // 切换显示状态 + toggle() { + if (this.isVisible) { + this.hide(); + } else { + this.show(); + } + } // 刷新提示词列表 + refreshPromptList() { + // 获取所有提示词用于标签更新 + const allPrompts = this.promptManager.getPrompts(); + + // 更新搜索栏的标签 + this.searchBar.updateTags(allPrompts); + // 设置原始数据,然后应用搜索关键词和标签筛选 + this.promptList.setPrompts(allPrompts); + if (this.currentSearchKeyword || (this.searchBar && this.searchBar.getSelectedTags().length > 0)) { + const selectedTags = this.searchBar ? this.searchBar.getSelectedTags() : []; + this.promptList.filterPrompts(this.currentSearchKeyword, selectedTags); + } + } // 新增:刷新所有组件的标签颜色和标签列表 + refreshAllTagColors() { + console.log('开始刷新所有组件的标签颜色和标签列表...'); + + // 首先刷新提示词列表的数据(确保已删除的标签从提示词中移除) + console.log('刷新提示词列表数据...'); + this.refreshPromptList(); + + // 刷新搜索栏中的标签列表和颜色(标签删除后需要重新生成标签列表) + if (this.searchBar) { + if (typeof this.searchBar.refreshTags === 'function') { + console.log('刷新搜索栏标签列表...'); + this.searchBar.refreshTags(); + } + if (typeof this.searchBar.refreshTagColors === 'function') { + console.log('刷新搜索栏标签颜色...'); + this.searchBar.refreshTagColors(); + } + } + + // 刷新列表中的标签颜色(此时数据已经是最新的了) + if (this.promptList && typeof this.promptList.refreshTagColors === 'function') { + console.log('刷新提示词列表标签颜色...'); + this.promptList.refreshTagColors(); + } + + // 刷新表单中的标签颜色 + if (this.promptForm && typeof this.promptForm.refreshTagColors === 'function') { + console.log('刷新提示词表单标签颜色...'); + this.promptForm.refreshTagColors(); + } + + console.log('所有组件标签颜色和标签列表刷新完成'); + } + // 显示插入模式选择对话框 + async showInsertDialog(title, message, callback) { + try { + const result = await this.dialogComponents.showInsertModeDialog(title, message); + if (callback && typeof callback === 'function') { + callback(result); + } + return result; + } catch (error) { + console.error('显示插入对话框失败:', error); + // 如果对话框失败,默认使用替换模式 + if (callback && typeof callback === 'function') { + callback('replace'); + } + return 'replace'; + } + } + + // 显示文本组件选择器对话框 + async showTextWidgetSelector(prompt) { + try { + const textWidgets = this.embedder.getAllTextWidgets(); + + if (textWidgets.length === 0) { + await this.dialogComponents.showErrorDialog('错误', '未找到可用的文本输入框'); + return; + } + + if (textWidgets.length === 1) { + // 只有一个,直接应用 + this.embedder.applyPromptToWidget(prompt, textWidgets[0]); + return; + } + + const selectedWidget = await this.dialogComponents.showWidgetSelector( + textWidgets, + (widget) => { + // 生成显示名称 + const name = widget.name || widget.options?.property || '未命名'; + const index = textWidgets.indexOf(widget) + 1; + return `${name} (输入框 ${index})`; + } + ); + + if (selectedWidget) { + this.embedder.applyPromptToWidget(prompt, selectedWidget); + } + } catch (error) { + console.error('显示文本组件选择器失败:', error); + await this.dialogComponents.showErrorDialog('错误', '显示选择器时发生错误'); + } + } +} \ No newline at end of file diff --git a/extensions/Prompt_Manager/styles/components/DialogComponents.js b/extensions/Prompt_Manager/styles/components/DialogComponents.js new file mode 100644 index 0000000..4da421e --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/DialogComponents.js @@ -0,0 +1,705 @@ +// 对话框组件 - 负责各种确认对话框和选择器 + +export class DialogComponents { + constructor() { + this.addStyles(); + } + + // 删除确认对话框 + showDeleteConfirm(promptName, onConfirm) { + return new Promise((resolve) => { + const modal = this.createModal('delete-confirm-modal'); + modal.innerHTML = ` +
+
+
+

⚠️ 确认删除

+ +
+
+

确定要删除提示词 "${this.escapeHtml(promptName)}" 吗?

+

此操作不可撤销!

+
+ + +
+
+
+
+ `; + + this.bindDialogEvents(modal, { + '.delete-confirm-btn': () => { + if (onConfirm) onConfirm(); + resolve(true); + this.closeModal(modal); + }, + '.delete-cancel-btn': () => { + resolve(false); + this.closeModal(modal); + } + }); + + this.showModal(modal); + }); + } // 插入模式选择对话框 + showInsertModeDialog(title = '选择插入模式', message = '检测到输入框中已有内容,请选择插入模式:') { + return new Promise((resolve) => { + let resolved = false; // 防止多次resolve + + const modal = this.createModal('insert-mode-modal'); + modal.innerHTML = ` +
+
+
+

${title}

+ +
+
+

${message}

+
+
+
📝
+
插入模式
+
在光标位置或文本开头插入提示词
+
+
+
➕
+
追加模式
+
在现有内容后面添加提示词
+
+
+
🔄
+
替换模式
+
删除原有内容,使用新提示词替换
+
+
+
+
+
+ `; + + const resolveOnce = (value) => { + if (!resolved) { + resolved = true; + this.closeModal(modal); + resolve(value); + } + }; + + this.bindDialogEvents(modal, { + '.option-card': (e) => { + const action = e.currentTarget.getAttribute('data-action'); + console.log('插入模式选择:', action); // 调试信息 + resolveOnce(action); + }, + '.dialog-close-btn': () => { + console.log('对话框关闭按钮点击'); // 调试信息 + resolveOnce(null); + } + }); + + this.showModal(modal); + }); + } + + // 文本组件选择器对话框 + showWidgetSelector(widgets, getDisplayName) { + return new Promise((resolve) => { + if (!Array.isArray(widgets) || widgets.length <= 1) { + resolve(widgets[0] || null); + return; + } + + const modal = this.createModal('widget-selector-modal'); + modal.innerHTML = ` +
+
+
+

🎯 选择文本输入框

+ +
+
+

该节点有多个文本输入框,请选择要应用提示词的输入框:

+
+ ${widgets.map((widget, index) => ` + + `).join('')} +
+
+
+
+ `; + + this.bindDialogEvents(modal, { + '.widget-option': (e) => { + const index = parseInt(e.currentTarget.getAttribute('data-index')); + resolve(widgets[index]); + this.closeModal(modal); + } + }); + + this.showModal(modal); + }); + } + + // 导入文件选择对话框 + showFileImportDialog(accept = '.json') { + return new Promise((resolve) => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = accept; + input.style.display = 'none'; + + input.addEventListener('change', (e) => { + const file = e.target.files[0]; + resolve(file); + document.body.removeChild(input); + }); + + input.addEventListener('cancel', () => { + resolve(null); + document.body.removeChild(input); + }); + + document.body.appendChild(input); + input.click(); + }); + } + + // 成功提示对话框 + showSuccessDialog(title, message, autoClose = 3000) { + return new Promise((resolve) => { + const modal = this.createModal('success-dialog-modal'); + modal.innerHTML = ` +
+
+
+

✅ ${title}

+ +
+
+

${message}

+ ${autoClose ? `
+
+
+
+ 对话框将在 ${Math.ceil(autoClose / 1000)} 秒后自动关闭 +
` : ''} +
+
+
+ `; + + this.bindDialogEvents(modal, { + '.dialog-close-btn': () => { + resolve(); + this.closeModal(modal); + } + }); + + this.showModal(modal); + + // 自动关闭 + if (autoClose) { + setTimeout(() => { + resolve(); + this.closeModal(modal); + }, autoClose); + } + }); + } + + // 错误提示对话框 + showErrorDialog(title, message) { + return new Promise((resolve) => { + const modal = this.createModal('error-dialog-modal'); + modal.innerHTML = ` +
+
+
+

❌ ${title}

+ +
+
+

${message}

+
+ +
+
+
+
+ `; + + this.bindDialogEvents(modal, { + '.error-ok-btn': () => { + resolve(); + this.closeModal(modal); + } + }); + + this.showModal(modal); + }); + } + + // 通用确认对话框 + showConfirmDialog(title, message, confirmText = '确定', cancelText = '取消') { + return new Promise((resolve) => { + const modal = this.createModal('confirm-dialog-modal'); + modal.innerHTML = ` +
+
+
+

${title}

+ +
+
+

${message}

+
+ + +
+
+
+
+ `; + + this.bindDialogEvents(modal, { + '.confirm-ok-btn': () => { + resolve(true); + this.closeModal(modal); + }, + '.confirm-cancel-btn': () => { + resolve(false); + this.closeModal(modal); + } + }); + + this.showModal(modal); + }); + } + + // 工具方法 + createModal(className) { + // 移除已存在的相同类型对话框 + const existing = document.querySelector(`.${className}`); + if (existing) { + existing.remove(); + } + + const modal = document.createElement('div'); + modal.className = className; + return modal; + } + + showModal(modal) { + document.body.appendChild(modal); + // 添加显示动画 + requestAnimationFrame(() => { + modal.classList.add('show'); + }); + } + + closeModal(modal) { + modal.classList.remove('show'); + setTimeout(() => { + if (modal.parentNode) { + modal.parentNode.removeChild(modal); + } + }, 300); + } bindDialogEvents(modal, eventMap) { + // 绑定自定义事件 + Object.entries(eventMap).forEach(([selector, handler]) => { + const elements = modal.querySelectorAll(selector); + elements.forEach(element => { + element.addEventListener('click', handler); + }); + }); + + // 只有在没有自定义关闭按钮处理时才绑定通用关闭事件 + if (!eventMap['.dialog-close-btn']) { + const closeBtn = modal.querySelector('.dialog-close-btn'); + if (closeBtn) { + closeBtn.addEventListener('click', () => this.closeModal(modal)); + } + } + const handleKeydown = (e) => { + if (e.key === 'Escape') { + // 如果有自定义关闭处理,使用它;否则直接关闭 + if (eventMap['.dialog-close-btn']) { + eventMap['.dialog-close-btn'](); + } else { + this.closeModal(modal); + } + document.removeEventListener('keydown', handleKeydown); + } + }; + document.addEventListener('keydown', handleKeydown); + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + truncateText(text, maxLength) { + if (!text || text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + } + + addStyles() { + const style = document.createElement('style'); + style.textContent = ` + /* 通用对话框样式 */ + .delete-confirm-modal, + .insert-mode-modal, + .widget-selector-modal, + .success-dialog-modal, + .error-dialog-modal, + .confirm-dialog-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 20000; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + } + + .delete-confirm-modal.show, + .insert-mode-modal.show, + .widget-selector-modal.show, + .success-dialog-modal.show, + .error-dialog-modal.show, + .confirm-dialog-modal.show { + opacity: 1; + visibility: visible; + } + + .dialog-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + backdrop-filter: blur(2px); + } + + .dialog-container { + background: #2b2b2b; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow: hidden; + transform: scale(0.9); + transition: transform 0.3s ease; + } + + .delete-confirm-modal.show .dialog-container, + .insert-mode-modal.show .dialog-container, + .widget-selector-modal.show .dialog-container, + .success-dialog-modal.show .dialog-container, + .error-dialog-modal.show .dialog-container, + .confirm-dialog-modal.show .dialog-container { + transform: scale(1); + } + + .dialog-header { + padding: 20px 24px; + border-bottom: 1px solid #404040; + display: flex; + justify-content: space-between; + align-items: center; + background: linear-gradient(135deg, #3a3a3a 0%, #2b2b2b 100%); + } + + .dialog-header.success { + background: linear-gradient(135deg, #43cf7c 0%, #38b86a 100%); + } + + .dialog-header.error { + background: linear-gradient(135deg, #ff5733 0%, #e04929 100%); + } + + .dialog-header h4 { + margin: 0; + color: #ffffff; + font-size: 16px; + font-weight: 600; + } + + .dialog-close-btn { + background: none; + border: none; + color: #fff; + font-size: 20px; + cursor: pointer; + padding: 4px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + transition: all 0.2s ease; + } + + .dialog-close-btn:hover { + background-color: rgba(255, 255, 255, 0.2); + } + + .dialog-content { + padding: 24px; + color: #e0e0e0; + line-height: 1.6; + } + + .dialog-content p { + margin: 0 0 16px 0; + } + + .warning-text { + color: #ff8c42; + font-weight: 500; + font-size: 14px; + } + + .dialog-actions { + display: flex; + gap: 12px; + margin-top: 20px; + } + + .btn-primary, + .btn-secondary, + .btn-danger { + padding: 10px 20px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s ease; + flex: 1; + } + + .btn-primary { + background: #2a82e4; + color: white; + } + + .btn-primary:hover { + background: #1c6bb7; + transform: translateY(-1px); + } + + .btn-secondary { + background: #6c757d; + color: white; + } + + .btn-secondary:hover { + background: #5a6268; + transform: translateY(-1px); + } + + .btn-danger { + background: #ff5733; + color: white; + } + + .btn-danger:hover { + background: #e04929; + transform: translateY(-1px); + } + + /* 选项卡片样式 */ + .option-cards { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 16px; + } + + .option-card { + background: #1a1a1a; + border: 2px solid #404040; + border-radius: 8px; + padding: 16px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 16px; + } + + .option-card:hover { + border-color: #2a82e4; + background: #262626; + transform: translateY(-2px); + } + + .option-icon { + font-size: 24px; + flex-shrink: 0; + } + + .option-title { + font-weight: 600; + color: #ffffff; + margin-bottom: 4px; + } + + .option-description { + font-size: 13px; + color: #888; + line-height: 1.4; + } + + /* 组件选择器样式 */ + .widget-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 16px; + } + + .widget-option { + background: #1a1a1a; + border: 2px solid #404040; + border-radius: 8px; + padding: 12px; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; + color: #e0e0e0; + } + + .widget-option:hover { + border-color: #2a82e4; + background: #262626; + } + + .widget-info { + display: flex; + flex-direction: column; + gap: 4px; + } + + .widget-name { + font-weight: 500; + color: #ffffff; + } + + .widget-preview { + font-size: 12px; + color: #888; + font-family: monospace; + } + + /* 自动关闭指示器 */ + .auto-close-indicator { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid #404040; + } + + .progress-bar { + width: 100%; + height: 3px; + background: #404040; + border-radius: 2px; + overflow: hidden; + margin-bottom: 8px; + } + + .progress-fill { + height: 100%; + background: #43cf7c; + width: 100%; + animation: shrink linear forwards; + transform-origin: right; + } + + @keyframes shrink { + from { + transform: scaleX(1); + } + to { + transform: scaleX(0); + } + } + + /* 响应式设计 */ + @media (max-width: 768px) { + .dialog-container { + margin: 20px; + max-width: none; + width: calc(100% - 40px); + } + + .dialog-content { + padding: 16px; + } + + .option-cards { + gap: 8px; + } + + .option-card { + padding: 12px; + flex-direction: column; + text-align: center; + gap: 8px; + } + + .dialog-actions { + flex-direction: column; + } + } + `; + + if (!document.head.querySelector('style[data-component="dialog-components"]')) { + style.setAttribute('data-component', 'dialog-components'); + document.head.appendChild(style); + } + } + + destroy() { + // 移除所有对话框 + const dialogs = document.querySelectorAll(` + .delete-confirm-modal, + .insert-mode-modal, + .widget-selector-modal, + .success-dialog-modal, + .error-dialog-modal, + .confirm-dialog-modal + `); + + dialogs.forEach(dialog => { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }); + + // 移除样式 + const style = document.head.querySelector('style[data-component="dialog-components"]'); + if (style) { + style.remove(); + } + } +} diff --git a/extensions/Prompt_Manager/styles/components/MainModal.js b/extensions/Prompt_Manager/styles/components/MainModal.js new file mode 100644 index 0000000..2c29657 --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/MainModal.js @@ -0,0 +1,261 @@ +// 主模态框组件 - 负责整体模态框结构和基础样式 + +export class MainModal { + constructor(title = '嵌入提示词管理') { + this.title = title; + this.modal = null; + this.isVisible = false; + this.createModal(); + this.bindEvents(); + } + + createModal() { + this.modal = document.createElement('div'); + this.modal.className = 'prompt-embedder-modal'; + this.modal.innerHTML = ` +
+
+
+

${this.title}

+ +
+
+ +
+
+
+ `; + + // 添加基础样式 + this.addStyles(); + + // 隐藏模态框 + this.modal.style.display = 'none'; + document.body.appendChild(this.modal); + } bindEvents() { + // 关闭按钮事件 + const closeBtn = this.modal.querySelector('.close-btn'); + closeBtn.addEventListener('click', () => this.hide()); + + // 移除点击遮罩层关闭功能,防止误点击 + // const overlay = this.modal.querySelector('.prompt-embedder-overlay'); + // overlay.addEventListener('click', (e) => { + // if (e.target === overlay) { + // this.hide(); + // } + // }); + + // ESC键关闭(保留快捷键) + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.isVisible) { + this.hide(); + } + }); + } + + show() { + this.modal.style.display = 'block'; + this.isVisible = true; + + // 添加显示动画 + requestAnimationFrame(() => { + this.modal.classList.add('show'); + }); + } + + hide() { + this.modal.classList.remove('show'); + this.isVisible = false; + + // 等待动画完成后隐藏 + setTimeout(() => { + this.modal.style.display = 'none'; + }, 300); + } + + toggle() { + if (this.isVisible) { + this.hide(); + } else { + this.show(); + } + } + + getContentContainer() { + return this.modal.querySelector('.prompt-embedder-content'); + } + + setTitle(title) { + this.title = title; + const titleElement = this.modal.querySelector('.prompt-embedder-header h3'); + if (titleElement) { + titleElement.textContent = title; + } + } + + addStyles() { + const style = document.createElement('style'); + style.textContent = ` + .prompt-embedder-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10000; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + } + + .prompt-embedder-modal.show { + opacity: 1; + visibility: visible; + } .prompt-embedder-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.6)); + display: flex; + justify-content: center; + align-items: center; + backdrop-filter: blur(8px); + padding: 20px; + box-sizing: border-box; + overflow-y: auto; + } .prompt-embedder-container { + background: linear-gradient(145deg, #2d2d30, #252528); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + width: 100%; + max-width: 1200px; + max-height: calc(100vh - 40px); + box-shadow: + 0 32px 64px rgba(0, 0, 0, 0.4), + 0 16px 32px rgba(0, 0, 0, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + overflow: hidden; + transform: scale(0.95) translateY(20px); + transition: all 0.4s ease-out; + position: relative; + margin: auto; + box-sizing: border-box; + }.prompt-embedder-modal.show .prompt-embedder-container { + transform: scale(1) translateY(0); + } + + .prompt-embedder-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 32px 20px; + background: linear-gradient(135deg, #3a3a3d, #2d2d30); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + z-index: 2; + } + + .prompt-embedder-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + } .prompt-embedder-header h3 { + color: #ffffff; + font-size: 20px; + font-weight: 600; + margin: 0; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + letter-spacing: 0.5px; + } + + .close-btn { + background: none; + border: none; + font-size: 28px; + color: #888; + cursor: pointer; + padding: 8px; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + transition: all 0.3s ease-out; + position: relative; + overflow: hidden; + } + + .close-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease; + z-index: -1; + } + + .close-btn:hover { + color: #fff; + transform: scale(1.1); + } + + .close-btn:hover::before { + width: 100%; + height: 100%; + } + + .close-btn:active { + transform: scale(0.95); + } .prompt-embedder-content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + background: #1e1e1e; + position: relative; + } + + .prompt-embedder-content::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + z-index: 1; + } + `; + + if (!document.head.querySelector('style[data-component="main-modal"]')) { + style.setAttribute('data-component', 'main-modal'); + document.head.appendChild(style); + } + } + + destroy() { + if (this.modal && this.modal.parentNode) { + this.modal.parentNode.removeChild(this.modal); + } + + // 移除样式 + const style = document.head.querySelector('style[data-component="main-modal"]'); + if (style) { + style.remove(); + } + } +} diff --git a/extensions/Prompt_Manager/styles/components/PromptForm.js b/extensions/Prompt_Manager/styles/components/PromptForm.js new file mode 100644 index 0000000..893a12a --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/PromptForm.js @@ -0,0 +1,1076 @@ +// 表单组件 - 负责添加/编辑提示词的表单 + +import { globalTagColorManager } from './TagColorManager.js'; + +export class PromptForm { + constructor(options = {}) { + this.onSubmit = options.onSubmit || null; + this.onCancel = options.onCancel || null; + this.getPrompts = options.getPrompts || null; // 新增:获取提示词数据的回调 + this.isEditing = false; + this.editingPrompt = null; + + this.container = null; + this.form = null; + this.titleElement = null; + this.createForm(); + } createForm() { this.container = document.createElement('div'); + this.container.className = 'prompt-form-section'; + // 初始状态:隐藏表单内容 + this.container.style.visibility = 'hidden'; + this.container.innerHTML = ` +

添加提示词

+
+
+ + + 建议使用简洁明了的名称,便于快速识别 +
+
+ + + 输入详细的提示词内容,支持多行文本 +
+ +
+ + +
标签用于快速分类和搜索提示词,每个标签用逗号分隔 + + +
+
+ + +
+
+ `; + + this.form = this.container.querySelector('.prompt-form'); + this.titleElement = this.container.querySelector('.form-title'); this.bindEvents(); + this.addStyles(); + } + + bindEvents() { + // 表单提交 + this.form.addEventListener('submit', (e) => { + e.preventDefault(); + this.handleSubmit(); + }); + + // 取消按钮 + const cancelBtn = this.container.querySelector('.cancel-btn'); + cancelBtn.addEventListener('click', () => { + this.handleCancel(); + }); + + // 输入验证 + this.bindValidationEvents(); + + // 标签输入处理 + this.bindTagsInput(); + + // ESC键取消 + this.container.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + this.handleCancel(); + } + }); + } + + bindValidationEvents() { + const nameInput = this.form.querySelector('input[name="name"]'); + const contentInput = this.form.querySelector('textarea[name="content"]'); + + // 实时验证 + nameInput.addEventListener('input', () => this.validateInput(nameInput)); + contentInput.addEventListener('input', () => this.validateInput(contentInput)); + + // 失焦验证 + nameInput.addEventListener('blur', () => this.validateInput(nameInput)); + contentInput.addEventListener('blur', () => this.validateInput(contentInput)); + } bindTagsInput() { + const tagsInput = this.form.querySelector('input[name="tags"]'); + const tagsSelectorBtn = this.form.querySelector('.tags-selector-btn'); + const availableTagsContainer = this.form.querySelector('.available-tags'); + + tagsInput.addEventListener('input', (e) => { + const value = e.target.value; + const tags = this.parseTags(value); + + e.target.classList.remove('error'); + this.hideInputError(e.target); + + // 实时更新标签预览 + this.updateTagsPreview(); + }); + + // 标签选择按钮 + tagsSelectorBtn.addEventListener('click', () => { + this.toggleAvailableTags(); + }); } + + // 辅助方法:将十六进制颜色转换为RGB + hexToRgb(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; + } + + validateInput(input) { + const value = input.value.trim(); + + if (input.hasAttribute('required') && !value) { + input.classList.add('error'); + this.showInputError(input, '此字段为必填项'); + return false; + } else { + input.classList.remove('error'); + this.hideInputError(input); + return true; + } + } + + showInputError(input, message) { + let errorElement = input.parentNode.querySelector('.error-message'); + if (!errorElement) { + errorElement = document.createElement('small'); + errorElement.className = 'error-message'; + input.parentNode.appendChild(errorElement); + } + errorElement.textContent = message; + } + + hideInputError(input) { + const errorElement = input.parentNode.querySelector('.error-message'); + if (errorElement) { + errorElement.remove(); + } + } updateTagsPreview() { + const tagsInput = this.form.querySelector('input[name="tags"]'); + const tags = this.parseTags(tagsInput.value); + + // 找到标签输入框所在的form-group容器 + const tagsFormGroup = tagsInput.closest('.form-group'); + + let previewElement = tagsFormGroup.querySelector('.tags-preview'); + if (!previewElement) { + previewElement = document.createElement('div'); + previewElement.className = 'tags-preview'; + // 将预览元素添加到form-group的末尾 + tagsFormGroup.appendChild(previewElement); + }if (tags.length > 0) { + previewElement.innerHTML = ` +
标签预览:
+
+ ${tags.map(tag => { + const color = globalTagColorManager.getTagColor(tag); + const colorRGB = globalTagColorManager.getTagColorRGB(tag); + return `${this.escapeHtml(tag)}`; + }).join('')} +
+ `; + previewElement.style.display = 'block'; + } else { + previewElement.style.display = 'none'; + } + } + + handleSubmit() { + const formData = this.getFormData(); + + // 验证表单 + if (!this.validateForm(formData)) { + return; + } + + if (this.onSubmit) { + this.onSubmit(formData, this.isEditing, this.editingPrompt); + } + } + + handleCancel() { + if (this.onCancel) { + this.onCancel(); + } + this.hide(); + } + + validateForm(formData) { + let isValid = true; + + // 验证名称 + if (!formData.name.trim()) { + const nameInput = this.form.querySelector('input[name="name"]'); + this.showInputError(nameInput, '请输入提示词名称'); + nameInput.classList.add('error'); + isValid = false; + } + + // 验证内容 + if (!formData.content.trim()) { + const contentInput = this.form.querySelector('textarea[name="content"]'); + this.showInputError(contentInput, '请输入提示词内容'); + contentInput.classList.add('error'); + isValid = false; + } // 验证标签数量 - 已移除限制,允许任意数量的标签 + + return isValid; + } + + getFormData() { + const formData = new FormData(this.form); + const tagsValue = formData.get('tags') || ''; + + return { + name: formData.get('name').trim(), + content: formData.get('content').trim(), + tags: this.parseTags(tagsValue) + }; + } parseTags(tagsString) { + if (!tagsString) return []; + + return tagsString + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0 && tag.length <= 20); // 只限制单个标签长度,不限制标签数量 + } show(mode = 'add', prompt = null) { + this.isEditing = mode === 'edit'; + this.editingPrompt = prompt; + + if (this.isEditing && prompt) { + this.titleElement.textContent = '编辑提示词'; + this.populateForm(prompt); + } else { + this.titleElement.textContent = '添加提示词'; + this.clearForm(); + } + + // 确保"从已有标签中选择"界面是隐藏的 + const availableTagsContainer = this.form.querySelector('.available-tags'); + if (availableTagsContainer) { + availableTagsContainer.style.display = 'none'; + } + + // 显示表单容器 + this.container.style.visibility = 'visible'; + + // 查找布局容器 + const layoutContainer = this.container.closest('.prompt-manager-layout'); + if (layoutContainer) { + // 添加展开类以触发布局动画 + layoutContainer.classList.add('form-expanded'); + } + + // 等待下一帧确保DOM更新 + requestAnimationFrame(() => { + // 聚焦到第一个输入框 + setTimeout(() => { + const firstInput = this.form.querySelector('input[name="name"]'); + if (firstInput) { + firstInput.focus(); + } + }, 100); + }); + } hide() { + // 隐藏表单容器 + this.container.style.visibility = 'hidden'; + this.clearForm(); + this.isEditing = false; + this.editingPrompt = null; + + // 确保"从已有标签中选择"界面被隐藏 + const availableTagsContainer = this.form.querySelector('.available-tags'); + if (availableTagsContainer) { + availableTagsContainer.style.display = 'none'; + } + + // 查找布局容器,检查是否还有其他内容需要显示 + const layoutContainer = this.container.closest('.prompt-manager-layout'); + if (layoutContainer) { + // 检查是否还有其他组件在显示(比如 TagManager) + const tagManager = layoutContainer.querySelector('.tag-manager-section'); + const isTagManagerVisible = tagManager && tagManager.classList.contains('active'); + + // 只有当没有其他内容显示时才移除展开类 + if (!isTagManagerVisible) { + layoutContainer.classList.remove('form-expanded'); + } + } + } + + isVisible() { + return this.container.style.visibility !== 'hidden'; + } populateForm(prompt) { + this.form.querySelector('input[name="name"]').value = prompt.name || ''; + this.form.querySelector('textarea[name="content"]').value = prompt.content || ''; + this.form.querySelector('input[name="tags"]').value = (prompt.tags || []).join(', '); + + // 确保"从已有标签中选择"界面是隐藏的 + const availableTagsContainer = this.form.querySelector('.available-tags'); + if (availableTagsContainer) { + availableTagsContainer.style.display = 'none'; + } + + this.updateTagsPreview(); + }clearForm() { + this.form.reset(); + + // 清除错误状态 + this.form.querySelectorAll('.error').forEach(el => el.classList.remove('error')); + this.form.querySelectorAll('.error-message').forEach(el => el.remove()); + + // 隐藏标签预览 + const previewElement = this.container.querySelector('.tags-preview'); + if (previewElement) { + previewElement.style.display = 'none'; + } + + // 隐藏"从已有标签中选择"界面 + const availableTagsContainer = this.form.querySelector('.available-tags'); + if (availableTagsContainer) { + availableTagsContainer.style.display = 'none'; + } + } + + getElement() { + return this.container; + } + + setOnSubmit(callback) { + this.onSubmit = callback; + } + + setOnCancel(callback) { + this.onCancel = callback; + } + + // 添加一个公开方法供外部调用清理 + setGetPrompts(getPromptsCallback) { + this.getPrompts = getPromptsCallback; + } + + // 手动清理未使用的标签 + cleanupUnusedTags() { + if (this.getPrompts && typeof this.getPrompts === 'function') { + const prompts = this.getPrompts(); + globalTagColorManager.cleanupUnusedTags(prompts); + } + } + + // 工具函数 + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + } addStyles() { + const style = document.createElement('style'); + style.textContent = ` .prompt-form-section { + width: 100%; + padding: 24px; + background: transparent; + border-left: none; + display: flex; + flex-direction: column; + height: 100%; + position: relative; + overflow-y: auto; + opacity: 0; + visibility: visible; + transition: opacity 0.3s ease-out 0.1s; + } + + /* 当父容器展开时,表单内容同步显示 */ + .prompt-manager-layout.form-expanded .prompt-form-section { + opacity: 1; + } + + .prompt-form-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 100%; + background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.2), transparent); + } + + .form-title { + margin: 0 0 24px 0; + color: #ffffff; + font-size: 18px; + font-weight: 600; + letter-spacing: 0.5px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + } + + .form-title::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 60px; + height: 2px; + background: linear-gradient(90deg, #0078d4, transparent); + border-radius: 1px; + } + + .prompt-form { + flex: 1; + display: flex; + flex-direction: column; + gap: 20px; + } + + .form-group { + display: flex; + flex-direction: column; + gap: 8px; + } + + .form-group label { + color: #e0e0e0; + font-weight: 500; + font-size: 14px; + letter-spacing: 0.25px; + margin-bottom: 4px; + } .form-group input, + .form-group textarea { + background: linear-gradient(145deg, #1a1a1a, #232323); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: #ffffff; + padding: 14px 16px; + font-size: 14px; + font-family: inherit; + transition: all 0.3s ease-out; + resize: vertical; + position: relative; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + } + + .form-group input:focus, + .form-group textarea:focus { + outline: none; + border-color: rgba(0, 120, 212, 0.6); + box-shadow: + 0 0 0 3px rgba(0, 120, 212, 0.15), + inset 0 1px 3px rgba(0, 0, 0, 0.2); + background: linear-gradient(145deg, #1e1e1e, #272727); + transform: translateY(-1px); + } + + .form-group input::placeholder, + .form-group textarea::placeholder { + color: #666; + font-weight: 300; + } + + .form-hint { + color: #888; + font-size: 12px; + font-weight: 400; + line-height: 1.4; + margin-top: 4px; + opacity: 0.8; + } + + .tags-input-wrapper { + display: flex; + gap: 8px; + align-items: stretch; + } + + .tags-input-wrapper input { + flex: 1; + } .tags-selector-btn { + background: linear-gradient(145deg, #404044, #35353a); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #e0e0e0; + padding: 0 16px; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + transition: all 0.3s ease-out; + white-space: nowrap; + display: flex; + align-items: center; + position: relative; + overflow: hidden; + } .tags-selector-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease-out; + z-index: -1; + } + + .tags-selector-btn:hover { + background: linear-gradient(145deg, #4a4a4e, #3f3f44); + color: #ffffff; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .tags-selector-btn:hover::before { + width: 200%; + height: 200%; + } .tags-selector-btn:active { + transform: translateY(0); + } /* 标签预览区域样式 */ + .tags-preview { + display: none; + margin-top: 16px; + padding: 12px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + } + + .tags-preview-label { + color: #e0e0e0; + font-size: 12px; + margin-bottom: 8px; + font-weight: 500; + } + + .tags-preview-list { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + + .tag-preview { + display: inline-block; + background: var(--tag-color, #2a82e4); + color: white; + padding: 4px 10px; + border-radius: 16px; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); + transition: all 0.3s ease-out; + position: relative; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + } + + .tags-preview-section { + margin-top: 12px; + padding: 12px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.05); + min-height: 60px; + display: flex; + flex-direction: column; + gap: 8px; + } + + .tags-preview-label { + color: #ccc; + font-size: 12px; + font-weight: 500; + margin: 0; + display: flex; + align-items: center; + gap: 6px; + } + + .tags-preview-label::before { + content: '👁️'; + font-size: 11px; + } + + .tags-preview-container { + flex: 1; + min-height: 36px; + display: flex; + align-items: center; + } + + .tags-preview-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; + min-height: 36px; + align-items: center; + } + + .tags-preview-list:empty::after { + content: '暂无标签预览'; + color: #666; + font-size: 12px; + font-style: italic; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 36px; + border: 1px dashed rgba(255, 255, 255, 0.1); + border-radius: 6px; + background: rgba(0, 0, 0, 0.2); + } + + .preview-tag { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--tag-color, #2a82e4); + color: white; + padding: 6px 12px; + border-radius: 14px; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + min-width: 60px; + max-width: 90px; + height: 26px; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + letter-spacing: 0.25px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + text-align: center; + transition: all 0.2s ease; + animation: tagFadeIn 0.3s ease-out; + } + + .preview-tag:hover { + transform: scale(1.05); + filter: brightness(1.1); + } + + @keyframes tagFadeIn { + from { + opacity: 0; + transform: scale(0.8) translateY(-10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } + } + + /* 可选标签区域样式 */ + .available-tags { + margin-top: 12px; + padding: 16px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + max-height: 200px; + overflow-y: auto; + } .available-tags.show { + display: block !important; + animation: slideDown 0.3s ease-out; + } + + @keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } .available-tags h5 { + margin: 0 0 12px 0; + color: #e0e0e0; + font-size: 13px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + } + + .available-tags h5::before { + content: '🏷️'; + font-size: 12px; + } + + .available-tags-header { + margin: 0 0 12px 0; + } + + .available-tags-header span { + color: #ccc; + font-size: 12px; + font-weight: 500; + margin: 0; + display: flex; + align-items: center; + gap: 6px; + } + + .available-tags-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + } .available-tag { + display: inline-flex; + align-items: center; + background: var(--tag-color, #2a82e4); + color: white; + padding: 6px 12px; + border-radius: 14px; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + min-width: 60px; + max-width: 90px; + height: 26px; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + letter-spacing: 0.25px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + text-align: center; + transition: all 0.2s ease; + cursor: pointer; + user-select: none; + transform: scale(1); + justify-content: center; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + }.available-tag:hover { + transform: scale(1.05); + filter: brightness(1.1); + } + + .available-tag.selected { + background: #666; + color: #999; + filter: grayscale(1); + } + + .available-tag.selected:hover { + transform: scale(1.05); + filter: grayscale(1) brightness(1.1); + } /* 表单操作按钮样式 */ + .form-actions { + display: flex; + gap: 12px; + margin-top: 24px; + padding-top: 20px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + } .form-actions button { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.3s ease-out; + position: relative; + overflow: hidden; + letter-spacing: 0.25px; + padding: 12px 20px; + white-space: nowrap; + text-align: center; + }.form-actions button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease-out; + z-index: -1; + } + + .form-actions button:hover::before { + width: 200%; + height: 200%; + } + + .btn-icon { + font-size: 16px; + line-height: 1; + } + + .btn-text { + white-space: nowrap; + } + + .save-btn { + background: linear-gradient(135deg, #28a745, #20873a); + color: white; + box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .save-btn:hover { + background: linear-gradient(135deg, #20873a, #1e7e34); + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(40, 167, 69, 0.4); + } + + .save-btn:active { + transform: translateY(0); + } + + .cancel-btn { + background: linear-gradient(145deg, #404044, #35353a); + color: #e0e0e0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .cancel-btn:hover { + background: linear-gradient(145deg, #4a4a4e, #3f3f44); + color: #ffffff; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .cancel-btn:active { + transform: translateY(0); + } + + /* 输入验证样式 */ + .form-group.error input, + .form-group.error textarea { + border-color: #dc3545; + box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.15); + } + + .form-group.error .form-hint { + color: #dc3545; + font-weight: 500; + } + + .form-group.success input, + .form-group.success textarea { + border-color: #28a745; + box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.15); + } + + /* 滚动条样式 */ + .available-tags::-webkit-scrollbar, + .prompt-form-section::-webkit-scrollbar { + width: 6px; + } + + .available-tags::-webkit-scrollbar-track, + .prompt-form-section::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + } + + .available-tags::-webkit-scrollbar-thumb, + .prompt-form-section::-webkit-scrollbar-thumb { + background: linear-gradient(145deg, #3a3a3a, #2a2a2a); + border-radius: 3px; + border: 1px solid rgba(255, 255, 255, 0.1); + } .available-tags::-webkit-scrollbar-thumb:hover, + .prompt-form-section::-webkit-scrollbar-thumb:hover { + background: linear-gradient(145deg, #4a4a4a, #3a3a3a); + } + + /* 响应式设计 */ + @media (max-width: 768px) { + .prompt-form-section { + padding: 16px; + } + + .form-actions { + flex-direction: column; + } + + .save-btn, .cancel-btn { + width: 100%; + } + } + `; + + if (!document.head.querySelector('style[data-component="prompt-form"]')) { + style.setAttribute('data-component', 'prompt-form'); + document.head.appendChild(style); + } + } + + destroy() { + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + + // 移除样式 + const style = document.head.querySelector('style[data-component="prompt-form"]'); + if (style) { + style.remove(); + } + } + + toggleAvailableTags() { + const availableTagsContainer = this.form.querySelector('.available-tags'); + const isVisible = availableTagsContainer.style.display !== 'none'; + + if (isVisible) { + availableTagsContainer.style.display = 'none'; + } else { + this.renderAvailableTags(); + availableTagsContainer.style.display = 'block'; + } + } renderAvailableTags() { + const availableTagsContainer = this.form.querySelector('.available-tags'); + + // 获取所有可用标签(包括手动创建的和提示词中的) + const allTags = globalTagColorManager.getAllTags(); + + const currentTags = this.getCurrentSelectedTags(); + + if (allTags.length === 0) { + availableTagsContainer.innerHTML = ` +
+ 暂无可选标签 + 先在标签管理中添加标签 +
+ `; + return; + }availableTagsContainer.innerHTML = ` +
+ 🏷️ 可选标签: +
+
${allTags.map(tag => { + const color = globalTagColorManager.getTagColor(tag); + const colorRGB = globalTagColorManager.getTagColorRGB(tag); + const isSelected = currentTags.includes(tag); + return ` + + ${this.escapeHtml(tag)} + + `; + }).join('')} +
+ `; + + this.bindAvailableTagsEvents(); + } bindAvailableTagsEvents() { + const availableTagsContainer = this.form.querySelector('.available-tags'); + + // 标签点击事件 + availableTagsContainer.querySelectorAll('.available-tag').forEach(tagElement => { + tagElement.addEventListener('click', () => { + const tagName = tagElement.dataset.tag; + this.toggleTagSelection(tagName); + }); + }); + } toggleTagSelection(tagName) { + const tagsInput = this.form.querySelector('input[name="tags"]'); + const currentTags = this.getCurrentSelectedTags(); + + if (currentTags.includes(tagName)) { + // 移除标签 + const newTags = currentTags.filter(tag => tag !== tagName); + tagsInput.value = newTags.join(', '); + } else { + // 添加标签 + const newTags = [...currentTags, tagName]; + tagsInput.value = newTags.join(', '); + } + + // 触发input事件来更新预览 + tagsInput.dispatchEvent(new Event('input')); + + // 重新渲染可选标签以更新选中状态 + this.renderAvailableTags(); + } + + getCurrentSelectedTags() { + const tagsInput = this.form.querySelector('input[name="tags"]'); + return this.parseTags(tagsInput.value); + } + + // 新增:刷新所有标签的颜色 + refreshTagColors() { + // 刷新标签预览的颜色 + const previewTags = this.container.querySelectorAll('.tag-preview'); + previewTags.forEach(tagElement => { + const tagName = tagElement.textContent; + if (tagName) { + const color = globalTagColorManager.getTagColor(tagName); + const colorRGB = globalTagColorManager.getTagColorRGB(tagName); + tagElement.style.setProperty('--tag-color', color); + tagElement.style.setProperty('--tag-color-rgb', colorRGB); + } + }); + + // 刷新可选标签的颜色 + const availableTags = this.container.querySelectorAll('.available-tag'); + availableTags.forEach(tagElement => { + const tagName = tagElement.dataset.tag; + if (tagName) { + const color = globalTagColorManager.getTagColor(tagName); const colorRGB = globalTagColorManager.getTagColorRGB(tagName); + tagElement.style.setProperty('--tag-color', color); + tagElement.style.setProperty('--tag-color-rgb', colorRGB); + } + }); + } +} diff --git a/extensions/Prompt_Manager/styles/components/PromptList.js b/extensions/Prompt_Manager/styles/components/PromptList.js new file mode 100644 index 0000000..22a9c72 --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/PromptList.js @@ -0,0 +1,812 @@ +// 列表组件 - 负责提示词列表的显示和交互 + +import { globalTagColorManager } from './TagColorManager.js'; + +export class PromptList { constructor(options = {}) { + this.prompts = []; + this.filteredPrompts = []; + this.currentSearchKeyword = ''; // 存储当前搜索关键词 + this.currentSelectedTags = []; // 存储当前选中的标签 + this.onEdit = options.onEdit || null; + this.onDelete = options.onDelete || null; + this.onApply = options.onApply || null; + this.onTagClick = options.onTagClick || null; + + this.container = null; + this.listContainer = null; + this.headerContainer = null; + this.createList(); + } + + createList() { + this.container = document.createElement('div'); + this.container.className = 'prompt-list-section'; this.container.innerHTML = ` +
+
+

提示词列表

+
+ 0 个项目 +
+
+
+
+ + +
+
+ + +
+
+
+
+ `; + + this.headerContainer = this.container.querySelector('.prompt-list-header'); + this.listContainer = this.container.querySelector('.prompt-list'); + + this.bindEvents(); + this.addStyles(); + } + + bindEvents() { + // 头部操作按钮事件将在主控制器中绑定 + // 这里只处理列表项的交互事件 + } setPrompts(prompts) { + this.prompts = Array.isArray(prompts) ? prompts : []; + // 重新应用当前搜索关键词和标签筛选 + if (this.currentSearchKeyword && this.currentSearchKeyword.trim() !== '') { + this.filterPrompts(this.currentSearchKeyword, this.currentSelectedTags); + } else if (this.currentSelectedTags && this.currentSelectedTags.length > 0) { + this.filterPrompts('', this.currentSelectedTags); + } else { + this.filteredPrompts = [...this.prompts]; + this.render(); + } + this.updateStats(); + } addPrompt(prompt) { + if (prompt && typeof prompt === 'object') { + this.prompts.push(prompt); + // 重新应用当前搜索关键词和标签筛选 + if (this.currentSearchKeyword && this.currentSearchKeyword.trim() !== '') { + this.filterPrompts(this.currentSearchKeyword, this.currentSelectedTags); + } else if (this.currentSelectedTags && this.currentSelectedTags.length > 0) { + this.filterPrompts('', this.currentSelectedTags); + } else { + this.filteredPrompts = [...this.prompts]; + this.render(); + } + this.updateStats(); + } + } updatePrompt(index, prompt) { + if (index >= 0 && index < this.prompts.length && prompt) { + this.prompts[index] = prompt; + // 重新应用当前搜索关键词和标签筛选 + if (this.currentSearchKeyword && this.currentSearchKeyword.trim() !== '') { + this.filterPrompts(this.currentSearchKeyword, this.currentSelectedTags); + } else if (this.currentSelectedTags && this.currentSelectedTags.length > 0) { + this.filterPrompts('', this.currentSelectedTags); + } else { + this.filteredPrompts = [...this.prompts]; + this.render(); + } + this.updateStats(); + } + } + + removePrompt(index) { + if (index >= 0 && index < this.prompts.length) { + this.prompts.splice(index, 1); // 重新应用当前搜索关键词和标签筛选 + if (this.currentSearchKeyword && this.currentSearchKeyword.trim() !== '') { + this.filterPrompts(this.currentSearchKeyword, this.currentSelectedTags); + } else if (this.currentSelectedTags && this.currentSelectedTags.length > 0) { + this.filterPrompts('', this.currentSelectedTags); + } else { + this.filteredPrompts = [...this.prompts]; + this.render(); + } + this.updateStats(); + } }filterPrompts(keyword, selectedTags = []) { + // 存储当前搜索关键词 + this.currentSearchKeyword = keyword || ''; + this.currentSelectedTags = selectedTags || []; + + // 应用关键词筛选和标签筛选 + this.filteredPrompts = this.prompts.filter(prompt => { + let keywordMatch = true; + let tagMatch = true; + + // 关键词筛选 + if (keyword && keyword.trim() !== '') { + const searchKeyword = keyword.toLowerCase(); + keywordMatch = false; + + // 搜索名称 + if (prompt.name && prompt.name.toLowerCase().includes(searchKeyword)) { + keywordMatch = true; + } + + // 搜索内容 + if (!keywordMatch && prompt.content && prompt.content.toLowerCase().includes(searchKeyword)) { + keywordMatch = true; + } + + // 搜索标签 + if (!keywordMatch && prompt.tags && Array.isArray(prompt.tags)) { + keywordMatch = prompt.tags.some(tag => + tag.toLowerCase().includes(searchKeyword) + ); + } + } + // 标签筛选 - 修复为AND逻辑(必须包含所有选中的标签) + if (selectedTags && selectedTags.length > 0) { + tagMatch = false; + if (prompt.tags && Array.isArray(prompt.tags)) { + // 检查是否包含所有选中的标签(AND逻辑) + tagMatch = selectedTags.every(selectedTag => + prompt.tags.includes(selectedTag) + ); + } + } + + return keywordMatch && tagMatch; + }); + + this.render(); + this.updateStats(); + return this.filteredPrompts.length; + } + + render() { + if (this.filteredPrompts.length === 0) { + this.renderEmptyState(); + } else { + this.renderPrompts(); + } + } renderEmptyState() { + // 判断是否在搜索状态 + const isSearching = this.currentSearchKeyword && this.currentSearchKeyword.trim() !== ''; + const hasPrompts = this.prompts.length > 0; + + let emptyIcon, emptyTitle, emptyDescription; + + if (isSearching && hasPrompts) { + // 有提示词但搜索无结果 + emptyIcon = '🔍'; + emptyTitle = '未找到相关内容'; + emptyDescription = `未找到包含"${this.currentSearchKeyword}"的提示词`; + } else { + // 没有提示词 + emptyIcon = '📝'; + emptyTitle = '暂无提示词'; + emptyDescription = '点击"+ 添加提示词"开始添加'; + } + + this.listContainer.innerHTML = ` +
+
${emptyIcon}
+

${emptyTitle}

+

${emptyDescription}

+
+ `; + } + + renderPrompts() { + this.listContainer.innerHTML = this.filteredPrompts.map((prompt, index) => { + const actualIndex = this.prompts.indexOf(prompt); + return this.createPromptItemHTML(prompt, actualIndex); + }).join(''); + + // 绑定列表项事件 + this.bindPromptItemEvents(); + } + + createPromptItemHTML(prompt, index) { const tagsHTML = prompt.tags && prompt.tags.length > 0 + ? `
+ ${prompt.tags.map(tag => { + const color = globalTagColorManager.getTagColor(tag); + const colorRGB = globalTagColorManager.getTagColorRGB(tag); + return `${this.escapeHtml(tag)}`; + }).join('')} +
` + : ''; + + return ` +
+
+
+
${this.escapeHtml(prompt.name || '')}
+ ${tagsHTML} +
+
+ + + +
+
+
${this.escapeHtml(prompt.content || '')}
+
+ `; + } + + bindPromptItemEvents() { + // 编辑按钮 + this.listContainer.querySelectorAll('.edit-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const index = parseInt(e.target.dataset.index); + if (this.onEdit && index >= 0) { + this.onEdit(index, this.prompts[index]); + } + }); + }); + + // 应用按钮 + this.listContainer.querySelectorAll('.apply-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const index = parseInt(e.target.dataset.index); + if (this.onApply && index >= 0) { + this.onApply(index, this.prompts[index]); + } + }); + }); + + // 删除按钮 + this.listContainer.querySelectorAll('.delete-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const index = parseInt(e.target.dataset.index); + if (this.onDelete && index >= 0) { + this.onDelete(index, this.prompts[index]); + } + }); + }); + + // 标签点击事件 + this.listContainer.querySelectorAll('.tag').forEach(tag => { + tag.addEventListener('click', (e) => { + const tagName = e.target.dataset.tag; + if (this.onTagClick && tagName) { + this.onTagClick(tagName); + } + }); + }); + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + getElement() { + return this.container; + } getHeaderActions() { + return { + importBtn: this.container.querySelector('.import-btn'), + exportBtn: this.container.querySelector('.export-btn'), + tagManagerBtn: this.container.querySelector('.tag-manager-btn'), + addBtn: this.container.querySelector('.add-prompt-btn') + }; + } + + // 设置回调函数 + setOnEdit(callback) { + this.onEdit = callback; + } + + setOnDelete(callback) { + this.onDelete = callback; + } + + setOnApply(callback) { + this.onApply = callback; + } + + setOnTagClick(callback) { + this.onTagClick = callback; + } + + addStyles() { + const style = document.createElement('style'); style.textContent = ` + .prompt-list-section { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background: #1e1e1e; + } .prompt-list-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 32px 20px; + background: linear-gradient(135deg, #2d2d30, #232326); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + z-index: 2; + box-sizing: border-box; + } + + .prompt-list-header::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + } + + .header-title { + display: flex; + flex-direction: column; + gap: 4px; + } + + .header-title h4 { + margin: 0; + color: #ffffff; + font-size: 18px; + font-weight: 600; + letter-spacing: 0.5px; + } + + .list-stats { + display: flex; + align-items: center; + gap: 8px; + } + + .total-count { + color: #999; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.25px; + } + + .header-actions { + display: flex; + gap: 16px; + align-items: center; + } + + .action-group { + display: flex; + gap: 8px; + align-items: center; + } + + .action-group.primary { + order: 1; + } + + .action-group.secondary { + order: 2; + position: relative; + } + + .action-group.secondary::before { + content: ''; + position: absolute; + left: -8px; + top: 50%; + transform: translateY(-50%); + width: 1px; + height: 24px; + background: rgba(255, 255, 255, 0.1); + } + + /* 按钮基础样式 */ + .prompt-list-header button { + display: flex; + align-items: center; + gap: 6px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + position: relative; + overflow: hidden; + letter-spacing: 0.25px; + } + + .prompt-list-header button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: all 0.3s ease; + z-index: -1; + } + + .prompt-list-header button:hover::before { + width: 200%; + height: 200%; + } + + .btn-icon { + font-size: 14px; + line-height: 1; + } + + .btn-text { + white-space: nowrap; + } /* 主要按钮样式 */ + .primary-btn { + background: linear-gradient(135deg, #0078d4, #106ebe); + color: white; + padding: 10px 20px; + min-height: 40px; + box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .primary-btn:hover { + background: linear-gradient(135deg, #106ebe, #005a9e); + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(0, 120, 212, 0.4); + } + + .primary-btn:active { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3); + } + + /* 次要按钮样式 */ + .secondary-btn { + background: linear-gradient(135deg, #404044, #35353a); + color: #e0e0e0; + padding: 10px 16px; + min-height: 40px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .secondary-btn:hover { + background: linear-gradient(135deg, #4a4a4e, #3f3f44); + color: #ffffff; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + /* 三级按钮样式 */ + .tertiary-btn { + background: rgba(255, 255, 255, 0.05); + color: #ccc; + padding: 10px 14px; + min-height: 40px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .tertiary-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; + border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); + }.tertiary-btn:active { + transform: translateY(0); + } /* 列表区域样式 */ + .prompt-list { + flex: 1; + overflow-y: auto; + padding: 24px 32px 24px 40px; + background: #1e1e1e; + position: relative; + box-sizing: border-box; + } .prompt-list::before { + content: ''; + position: absolute; + top: 0; + left: 32px; + right: 32px; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + } + + .empty-state { + text-align: center; + padding: 80px 20px; + color: #888; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.02), transparent); + border-radius: 12px; + border: 1px dashed rgba(255, 255, 255, 0.1); + margin: 40px 0; + } + + .empty-state-icon { + font-size: 64px; + margin-bottom: 20px; + opacity: 0.6; + filter: grayscale(1); + } + + .empty-state p { + margin: 12px 0; + font-size: 14px; + line-height: 1.5; + } + + .empty-state p:first-of-type { + font-size: 16px; + font-weight: 500; + color: #aaa; + } + + .prompt-item { + background: linear-gradient(145deg, #2a2a2a, #232323); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 20px; + margin-bottom: 16px; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + position: relative; + overflow: hidden; + } + + .prompt-item::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + opacity: 0; + transition: opacity 0.3s ease; + } + + .prompt-item:hover { + border-color: rgba(0, 120, 212, 0.4); + transform: translateY(-2px); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + 0 2px 8px rgba(0, 120, 212, 0.1); + background: linear-gradient(145deg, #2e2e2e, #272727); + } + + .prompt-item:hover::before { + opacity: 1; + } + + .prompt-item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; + gap: 16px; + } + + .prompt-item-title-section { + flex: 1; + min-width: 0; + } + + .prompt-item-name { + font-weight: 600; + color: #ffffff; + font-size: 16px; + margin-bottom: 10px; + word-break: break-word; + line-height: 1.4; + letter-spacing: 0.25px; + } + + .prompt-item-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; + } .tag { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--tag-color, #2a82e4); + color: white; + padding: 6px 12px; + border-radius: 16px; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + min-width: 60px; + max-width: 90px; + height: 28px; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + user-select: none; + position: relative; + letter-spacing: 0.25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + text-align: center; + border: 1px solid rgba(255, 255, 255, 0.1); + transform: scale(1); + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + }.tag::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent); + border-radius: inherit; + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + } .tag:hover { + box-shadow: + 0 0 10px rgba(var(--tag-color-rgb), 0.3), + 0 4px 15px rgba(0, 0, 0, 0.2); + transform: scale(1.05); + border-color: rgba(var(--tag-color-rgb), 0.4); + filter: brightness(1.1) saturate(1.05); + }.tag:hover::before { + opacity: 1; + } + + .prompt-item-actions { + display: flex; + gap: 8px; + flex-shrink: 0; + } + + .edit-btn, .apply-btn, .delete-btn { + background: #383838; + border: none; + color: white; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + transition: all 0.2s ease; + white-space: nowrap; + } + + .edit-btn:hover { + background: #ffc300; + color: #000; + transform: translateY(-1px); + } + + .apply-btn:hover { + background: #43cf7c; + transform: translateY(-1px); + } + + .delete-btn { + background: #8b3a3a; + } + + .delete-btn:hover { + background: #ff5733; + transform: translateY(-1px); + } .prompt-item-content { + color: #c4c4c4; + line-height: 1.5; + background: linear-gradient(145deg, #1a1a1a, #232323); + padding: 16px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.05); + white-space: pre-wrap; + word-break: break-word; + max-height: 200px; + overflow-y: auto; + margin: 0 auto; + text-align: left; + font-size: 14px; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + width: 100%; + max-width: 100%; + } + + /* 响应式设计 */ + @media (max-width: 768px) { + .prompt-list-header { + flex-direction: column; + gap: 12px; + align-items: stretch; + } + + .header-actions { + justify-content: space-between; + } + + .prompt-item { + padding: 12px; + } + + .prompt-item-header { + flex-direction: column; + gap: 12px; + } + + .prompt-item-actions { + justify-content: flex-end; + } + } + `; + + if (!document.head.querySelector('style[data-component="prompt-list"]')) { + style.setAttribute('data-component', 'prompt-list'); + document.head.appendChild(style); + } + } + + updateStats() { + const totalElement = this.container.querySelector('.total-count'); + if (totalElement) { + totalElement.textContent = `${this.filteredPrompts.length} 个项目`; + } + } // 新增:刷新所有标签的颜色 + refreshTagColors() { + console.log('PromptList: 刷新标签颜色和删除无效标签...'); + + // 重新渲染整个列表以确保已删除的标签被正确移除 + // 这比仅仅更新颜色更可靠,能够处理标签删除的情况 + this.render(); + + console.log('PromptList: 标签颜色刷新和无效标签清理完成'); + } + + // 新增:完全重新渲染列表(保持当前筛选状态) + forceRefresh() { + this.render(); + } + + // 清除所有筛选条件 + clearFilters() { + this.currentSearchKeyword = ''; + this.currentSelectedTags = []; + this.filteredPrompts = [...this.prompts]; + this.render(); + this.updateStats(); + } + + // 获取当前筛选状态 + getFilterState() { + return { + keyword: this.currentSearchKeyword, + selectedTags: this.currentSelectedTags + }; + } + + destroy() { + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + + // 移除样式 + const style = document.head.querySelector('style[data-component="prompt-list"]'); + if (style) { + style.remove(); + } + } +} diff --git a/extensions/Prompt_Manager/styles/components/SearchBar.js b/extensions/Prompt_Manager/styles/components/SearchBar.js new file mode 100644 index 0000000..e13b0bb --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/SearchBar.js @@ -0,0 +1,700 @@ +// 搜索栏组件 - 负责提示词搜索功能和标签筛选 + +import { globalTagColorManager } from './TagColorManager.js'; + +export class SearchBar { + constructor(onSearch = null, onTagFilter = null) { + this.onSearch = onSearch; + this.onTagFilter = onTagFilter; // 标签筛选回调 + this.currentKeyword = ''; + this.selectedTags = new Set(); // 选中的标签 + this.allTags = new Set(); // 所有可用标签 + this.searchInput = null; + this.container = null; this.tagsContainer = null; + this.debounceTimer = null; + this.createSearchBar(); + }createSearchBar() { + this.container = document.createElement('div'); + this.container.className = 'search-section'; + this.container.innerHTML = ` +
+ +
🔍
+ +
+
+ +
+ + `; + + this.searchInput = this.container.querySelector('.search-input'); + this.clearBtn = this.container.querySelector('.clear-search-btn'); + this.statsContainer = this.container.querySelector('.search-stats'); + this.resultsCount = this.container.querySelector('.search-results-count'); + this.tagsContainer = this.container.querySelector('.tags-filter-container'); + this.activeFiltersCount = this.container.querySelector('.active-filters-count'); + this.filterCount = this.container.querySelector('.filter-count'); + + this.bindEvents(); + this.addStyles(); + } + + bindEvents() { + // 搜索输入事件(带防抖) + this.searchInput.addEventListener('input', (e) => { + const keyword = e.target.value.trim(); + this.updateClearButton(keyword); + + // 防抖处理 + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + this.debounceTimer = setTimeout(() => { + this.handleSearch(keyword); + }, 300); + }); + + // 回车键搜索 + this.searchInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + this.handleSearch(this.searchInput.value.trim()); + } + }); + + // 清除搜索 + this.clearBtn.addEventListener('click', () => { + this.clearSearch(); + }); + + // 聚焦输入框时的样式 + this.searchInput.addEventListener('focus', () => { + this.container.querySelector('.search-container').classList.add('focused'); + }); + + this.searchInput.addEventListener('blur', () => { + this.container.querySelector('.search-container').classList.remove('focused'); + }); + } handleSearch(keyword) { + this.currentKeyword = keyword; + + // 同时应用搜索和标签筛选 + if (this.onTagFilter && typeof this.onTagFilter === 'function') { + const results = this.onTagFilter(keyword, Array.from(this.selectedTags)); + this.updateSearchStats(results); + } else if (this.onSearch && typeof this.onSearch === 'function') { + // 兼容旧的搜索回调 + const results = this.onSearch(keyword); + this.updateSearchStats(results); + } + } + + updateClearButton(keyword) { + if (keyword) { + this.clearBtn.style.display = 'block'; + } else { + this.clearBtn.style.display = 'none'; + } + } + + updateSearchStats(results) { + if (this.currentKeyword) { + this.statsContainer.style.display = 'block'; + const count = Array.isArray(results) ? results.length : (results || 0); + this.resultsCount.textContent = `找到 ${count} 个结果`; + } else { + this.statsContainer.style.display = 'none'; + } + } clearSearch() { + this.searchInput.value = ''; + this.currentKeyword = ''; + this.clearBtn.style.display = 'none'; + this.statsContainer.style.display = 'none'; + this.selectedTags.clear(); // 同时清除标签筛选 + this.renderTags(); + this.updateActiveFiltersDisplay(); + this.handleFilter(); + this.searchInput.focus(); + } + + setKeyword(keyword) { + this.searchInput.value = keyword; + this.currentKeyword = keyword; + this.updateClearButton(keyword); + this.handleSearch(keyword); + } + + getKeyword() { + return this.currentKeyword; + } + + setOnSearch(callback) { + this.onSearch = callback; + } + + focus() { + this.searchInput.focus(); + } + + addStyles() { const style = document.createElement('style'); + style.textContent = ` .search-section { + padding: 24px 32px 20px; + background: linear-gradient(135deg, #2d2d30, #232326); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + z-index: 2; + box-sizing: border-box; + } + + .search-section::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + } + + .search-container { + position: relative; + display: flex; + align-items: center; + background: linear-gradient(145deg, #1e1e1e, #2a2a2a); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + box-shadow: + inset 0 1px 3px rgba(0, 0, 0, 0.2), + 0 1px 3px rgba(0, 0, 0, 0.1); + overflow: hidden; + } + + .search-container::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + opacity: 0; + transition: opacity 0.3s ease; + } + + .search-container.focused { + border-color: rgba(0, 120, 212, 0.6); + box-shadow: + 0 0 0 3px rgba(0, 120, 212, 0.15), + inset 0 1px 3px rgba(0, 0, 0, 0.2), + 0 4px 12px rgba(0, 120, 212, 0.1); + transform: translateY(-1px); + } + + .search-container.focused::before { + opacity: 1; + } + + .search-input { + flex: 1; + background: transparent; + border: none; + color: #ffffff; + padding: 14px 16px; + font-size: 14px; + font-weight: 400; + outline: none; + min-width: 0; + letter-spacing: 0.25px; + } + + .search-input::placeholder { + color: #888; + font-weight: 300; + } + + .search-icon { + color: #888; + padding: 0 16px 0 16px; + font-size: 16px; + pointer-events: none; + transition: color 0.3s ease; + } + + .search-container.focused .search-icon { + color: #0078d4; + } + + .clear-search-btn { + background: none; + border: none; + color: #888; + cursor: pointer; + padding: 8px 16px; + font-size: 16px; + transition: all 0.3s ease; + border-radius: 6px; + margin-right: 4px; + opacity: 0; + transform: scale(0.8); + pointer-events: none; + } + + .clear-search-btn.visible { + opacity: 1; + transform: scale(1); + pointer-events: auto; + } + + .clear-search-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); + transform: scale(1.1); + } .clear-search-btn:active { + transform: scale(0.95); + } + + .search-stats { + margin-top: 12px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 4px; + } + + .search-results-count { + color: #999; + font-size: 12px; + font-weight: 400; + letter-spacing: 0.25px; + } + + .active-filters-count { + color: #4a9eff; + font-size: 12px; + font-weight: 500; + padding: 2px 8px; + background: rgba(74, 158, 255, 0.1); + border-radius: 6px; + border: 1px solid rgba(74, 158, 255, 0.2); + } /* 标签筛选样式 */ + .tags-filter-container { + margin-top: 12px; + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + } + + .tags-filter-container:empty { + display: none; + } .tags-header { + display: none; + } + + .tags-title { + color: #e0e0e0; + font-size: 13px; + font-weight: 600; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 6px; + } + + .tags-title::before { + content: '🏷️'; + font-size: 12px; + } .clear-tags-btn { + background: linear-gradient(145deg, #2a2a2a, #1e1e1e); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #ccc; + padding: 6px 12px; + font-size: 11px; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + font-weight: 500; + letter-spacing: 0.25px; + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-right: 8px; + } + + .clear-tags-btn:hover { + border-color: rgba(255, 255, 255, 0.2); + color: #fff; + background: linear-gradient(145deg, #3a3a3a, #2e2e2e); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + } + + .clear-tags-btn:active { + transform: translateY(0); + } .tags-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + } .filter-tag { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--tag-color, #2a82e4); + color: white; + padding: 6px 12px; + border-radius: 16px; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); + user-select: none; + border: 1px solid rgba(255, 255, 255, 0.1); + min-width: 60px; + max-width: 90px; + height: 28px; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + letter-spacing: 0.25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + text-align: center; + transform: scale(1); + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + }.filter-tag::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent); + border-radius: inherit; + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + } .filter-tag:hover { + box-shadow: + 0 0 10px rgba(var(--tag-color-rgb), 0.3), + 0 4px 15px rgba(0, 0, 0, 0.2); + transform: scale(1.05); + border-color: rgba(var(--tag-color-rgb), 0.4); + filter: brightness(1.1) saturate(1.05); + }.filter-tag:hover::before { + opacity: 1; + } .filter-tag.selected { + background: var(--tag-color, #2a82e4); + box-shadow: + 0 0 12px rgba(var(--tag-color-rgb), 0.4), + 0 4px 15px rgba(0, 0, 0, 0.3), + 0 0 0 2px rgba(255, 255, 255, 0.8); + border-color: rgba(var(--tag-color-rgb), 0.8); + transform: scale(1.08); + font-weight: 600; + color: #ffffff; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + position: relative; + z-index: 2; + filter: brightness(1.15) saturate(1.1); + border: 2px solid rgba(255, 255, 255, 0.9); + animation: tagSelectPulse 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + } .filter-tag.selected:hover { + background: var(--tag-color, #2a82e4); + box-shadow: + 0 0 16px rgba(var(--tag-color-rgb), 0.5), + 0 6px 20px rgba(0, 0, 0, 0.3), + 0 0 0 2px rgba(255, 255, 255, 0.9); + transform: scale(1.12); + filter: brightness(1.25) saturate(1.15); + } + + @keyframes tagSelectPulse { + 0% { + transform: scale(1); + box-shadow: + 0 0 8px rgba(var(--tag-color-rgb), 0.3), + 0 2px 8px rgba(0, 0, 0, 0.2); + } + 50% { + transform: scale(1.15); + box-shadow: + 0 0 20px rgba(var(--tag-color-rgb), 0.6), + 0 6px 20px rgba(0, 0, 0, 0.3), + 0 0 0 3px rgba(255, 255, 255, 1); + } + 100% { + transform: scale(1.08); + box-shadow: + 0 0 12px rgba(var(--tag-color-rgb), 0.4), + 0 4px 15px rgba(0, 0, 0, 0.3), + 0 0 0 2px rgba(255, 255, 255, 0.8); + } } + + .no-tags { + display: none; + } + + /* 响应式设计 */ + @media (max-width: 768px) { + .search-input { + padding: 10px 12px; + font-size: 16px; /* 防止iOS缩放 */ + } + + .search-input::placeholder { + font-size: 14px; + } + } + `; + + if (!document.head.querySelector('style[data-component="search-bar"]')) { + style.setAttribute('data-component', 'search-bar'); + document.head.appendChild(style); + } + } // 标签相关方法 + updateTags(prompts) { + // 收集所有标签 + this.allTags.clear(); + + // 1. 从提示词中收集标签 + prompts.forEach(prompt => { + if (prompt.tags && Array.isArray(prompt.tags)) { + prompt.tags.forEach(tag => { + if (tag && tag.trim()) { + this.allTags.add(tag.trim()); + } + }); + } + }); + + // 2. 从TagColorManager中获取所有手动创建的标签(包括未使用的) + const allManagedTags = globalTagColorManager.getAllTags(); + allManagedTags.forEach(tag => { + this.allTags.add(tag); + }); + + // 更新全局标签颜色管理器 + globalTagColorManager.updateFromPrompts(prompts); + + this.renderTags(); + } + + generateRandomColor() { + // 使用全局标签颜色管理器 + return globalTagColorManager.generateRandomColor(); + } renderTags() { + if (!this.tagsContainer) return; + + if (this.allTags.size === 0) { + this.tagsContainer.innerHTML = ''; + return; + } + + const sortedTags = Array.from(this.allTags).sort(); + + // 如果有选中的标签,在标签列表前添加清除按钮 + const clearButton = this.selectedTags.size > 0 + ? `` + : ''; + + this.tagsContainer.innerHTML = ` + ${clearButton} + ${sortedTags.map(tag => this.createTagElement(tag)).join('')} + `; + + this.bindTagEvents(); + }createTagElement(tag) { + const isSelected = this.selectedTags.has(tag); + const color = globalTagColorManager.getTagColor(tag); + const colorRGB = globalTagColorManager.getTagColorRGB(tag); + + return ` + + ${this.escapeHtml(tag)} + + `; + } bindTagEvents() { + // 标签点击事件 + this.tagsContainer.querySelectorAll('.filter-tag').forEach(tagElement => { + tagElement.addEventListener('click', () => { + const tag = tagElement.dataset.tag; + this.toggleTag(tag); + }); + }); + + // 清除标签筛选 + const clearTagsBtn = this.tagsContainer.querySelector('.clear-tags-btn'); + if (clearTagsBtn) { + clearTagsBtn.addEventListener('click', () => { + this.clearTagFilters(); + }); + } + } + + toggleTag(tag) { + if (this.selectedTags.has(tag)) { + this.selectedTags.delete(tag); + } else { + this.selectedTags.add(tag); + } + + this.renderTags(); + this.updateActiveFiltersDisplay(); + this.handleFilter(); + } + + clearTagFilters() { + this.selectedTags.clear(); + this.renderTags(); + this.updateActiveFiltersDisplay(); + this.handleFilter(); + } + + updateActiveFiltersDisplay() { + if (this.selectedTags.size > 0) { + this.activeFiltersCount.style.display = 'inline'; + this.filterCount.textContent = this.selectedTags.size; + } else { + this.activeFiltersCount.style.display = 'none'; + } + } + + handleFilter() { + // 同时应用搜索和标签筛选 + if (this.onTagFilter && typeof this.onTagFilter === 'function') { + const results = this.onTagFilter(this.currentKeyword, Array.from(this.selectedTags)); + this.updateSearchStats(results); + } + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + destroy() { + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + + // 移除样式 + const style = document.head.querySelector('style[data-component="search-bar"]'); + if (style) { + style.remove(); + } + } + + // 公开方法 + getSelectedTags() { + return Array.from(this.selectedTags); + } + + hasTagsSelected() { + return this.selectedTags.size > 0; + } + + selectTag(tag) { + if (this.allTags.has(tag) && !this.selectedTags.has(tag)) { + this.selectedTags.add(tag); + this.renderTags(); + this.updateActiveFiltersDisplay(); + this.handleFilter(); + } + } + + getCurrentKeyword() { + return this.currentKeyword; + } + + // 获取组件元素 + getElement() { + return this.container; + } // 刷新标签筛选区域(从TagColorManager获取所有标签) + refreshTags() { + console.log('SearchBar: 开始刷新标签列表...'); + + // 从全局标签颜色管理器获取最新的标签列表 + const allTags = globalTagColorManager.getAllTags(); + console.log('SearchBar: 从TagColorManager获取到的标签:', allTags); + + // 更新本地标签集合 + this.allTags = new Set(allTags); + + // 清理已删除的标签选择状态 + const deletedTags = []; + this.selectedTags.forEach(selectedTag => { + if (!this.allTags.has(selectedTag)) { + deletedTags.push(selectedTag); + } + }); + + // 移除已删除标签的选择状态 + deletedTags.forEach(tag => { + console.log(`SearchBar: 移除已删除标签的选择状态: ${tag}`); + this.selectedTags.delete(tag); + }); + + // 重新渲染标签UI + console.log('SearchBar: 重新渲染标签UI...'); + this.renderTags(); + + // 如果有选中状态变化,重新应用筛选 + if (deletedTags.length > 0) { + console.log('SearchBar: 检测到标签删除,重新应用筛选...'); + this.updateActiveFiltersDisplay(); + this.handleFilter(); + } + + console.log('SearchBar: 标签列表刷新完成,当前标签:', Array.from(this.allTags)); + } + + // 新增:刷新所有标签的颜色 + refreshTagColors() { + // 重新渲染所有标签的颜色 + this.tagsContainer.querySelectorAll('.filter-tag').forEach(tagElement => { + const tagName = tagElement.dataset.tag; + if (tagName) { + const color = globalTagColorManager.getTagColor(tagName); + const colorRGB = globalTagColorManager.getTagColorRGB(tagName); + tagElement.style.setProperty('--tag-color', color); + tagElement.style.setProperty('--tag-color-rgb', colorRGB); + } + }); + } +} diff --git a/extensions/Prompt_Manager/styles/components/TagColorManager.js b/extensions/Prompt_Manager/styles/components/TagColorManager.js new file mode 100644 index 0000000..f5317bb --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/TagColorManager.js @@ -0,0 +1,288 @@ +// 标签颜色管理器 - 完全基于后端JSON文件同步,移除localStorage逻辑 + +export class TagColorManager { + constructor() { + this.colorMap = new Map(); // 标签名 -> 颜色映射 + this.usedColors = new Set(); // 已使用的颜色 + this.loadFromBackend(); // 启动时从后端加载 + } + + // 从后端API加载标签数据 + async loadFromBackend() { + try { + const response = await fetch('/dd_nodes/load_tags'); + if (response.ok) { + const result = await response.json(); + if (result.success && result.tags) { + // 更新颜色映射 + this.colorMap = new Map(Object.entries(result.tags)); + this.usedColors = new Set(Object.values(result.tags)); + + console.log(`从后端加载了 ${Object.keys(result.tags).length} 个标签颜色`); + } else { + console.log('后端没有标签数据,使用空映射'); + } + } else { + console.warn('从后端加载标签失败,使用空映射'); + } + } catch (error) { + console.warn('从后端加载标签失败,使用空映射:', error); + } + } + + // 直接同步到后端,移除localStorage逻辑 + async saveToBackend() { + try { + const colorData = Object.fromEntries(this.colorMap); + const response = await fetch('/dd_nodes/save_tags', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + tags: colorData + }) + }); + + if (response.ok) { + const result = await response.json(); + if (result.success) { + console.log('标签数据已同步到后端:', colorData); + return true; + } else { + console.error('后端同步失败:', result.error); + return false; + } + } else { + console.error('后端同步请求失败:', response.status); + return false; + } + } catch (error) { + console.error('同步标签到后端失败:', error); + return false; + } + } + + // 获取标签颜色(只读取,不自动创建) + getTagColor(tagName) { + if (!tagName || typeof tagName !== 'string') { + return '#666'; // 默认颜色 + } + + const normalizedTag = tagName.trim(); + + // 如果标签存在,返回其颜色 + if (this.colorMap.has(normalizedTag)) { + return this.colorMap.get(normalizedTag); + } + + // 如果标签不存在,返回默认颜色,但不自动创建标签 + return '#666'; // 默认颜色,用于预览 + } + + // 获取或创建标签颜色(用于实际保存标签时) + async getOrCreateTagColor(tagName) { + if (!tagName || typeof tagName !== 'string') { + return '#666'; // 默认颜色 + } + + const normalizedTag = tagName.trim(); + + // 如果标签已存在,直接返回颜色 + if (this.colorMap.has(normalizedTag)) { + return this.colorMap.get(normalizedTag); + } + + // 标签不存在时,创建新颜色 + const newColor = this.generateUniqueColor(); + this.colorMap.set(normalizedTag, newColor); + this.usedColors.add(newColor); + + console.log(`为新标签 "${normalizedTag}" 生成颜色: ${newColor}`); + + return newColor; + } + + // 设置标签颜色(用于标签管理功能) + setTagColor(tagName, color) { + if (!tagName || typeof tagName !== 'string') return false; + + const normalizedTag = tagName.trim(); + const oldColor = this.colorMap.get(normalizedTag); + + console.log(`设置标签颜色: ${normalizedTag} -> ${color}`); + + // 移除旧颜色 + if (oldColor) { + this.usedColors.delete(oldColor); + } + + // 设置新颜色 + this.colorMap.set(normalizedTag, color); + this.usedColors.add(color); + + return true; // 不立即同步,由调用方统一处理 + } + + // 获取标签的RGB值(用于CSS动画中的rgba()) + getTagColorRGB(tagName) { + const hslColor = this.getTagColor(tagName); + return this.hslToRgb(hslColor); + } + + // 将HSL颜色转换为RGB值字符串(如:"255, 100, 50") + hslToRgb(hslString) { + // 解析HSL字符串,如 "hsl(120, 75%, 50%)" + const match = hslString.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/); + if (!match) return '102, 102, 102'; // 默认灰色的RGB + + const h = parseInt(match[1]) / 360; + const s = parseInt(match[2]) / 100; + const l = parseInt(match[3]) / 100; + + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + }; + + let r, g, b; + + if (s === 0) { + r = g = b = l; // 无饱和度时为灰色 + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return `${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}`; + } + + // 生成唯一的随机颜色 + generateUniqueColor() { + let attempts = 0; + let color; + + do { + color = this.generateRandomColor(); + attempts++; + } while (this.usedColors.has(color) && attempts < 50); + + return color; + } + + // 生成较明亮的随机颜色 + generateRandomColor() { + const hue = Math.floor(Math.random() * 360); + const saturation = 65 + Math.floor(Math.random() * 25); // 65-90% + const lightness = 45 + Math.floor(Math.random() * 15); // 45-60% + return `hsl(${hue}, ${saturation}%, ${lightness}%)`; + } + + // 获取所有标签颜色映射 + getAllColors() { + return new Map(this.colorMap); + } + + // 获取所有标签名称 + getAllTags() { + const tags = Array.from(this.colorMap.keys()).sort(); + console.log('获取所有标签:', tags); + return tags; + } + + // 删除标签 + deleteTag(tagName) { + if (!tagName || typeof tagName !== 'string') return false; + + const normalizedTag = tagName.trim(); + const color = this.colorMap.get(normalizedTag); + + console.log(`尝试删除标签: ${normalizedTag},当前颜色映射:`, Object.fromEntries(this.colorMap)); + + if (color) { + this.colorMap.delete(normalizedTag); + this.usedColors.delete(color); + + console.log(`标签 ${normalizedTag} 已删除,剩余标签:`, Object.fromEntries(this.colorMap)); + return true; + } + + console.log(`标签 ${normalizedTag} 不存在,无法删除`); + return false; + } + + // 清除所有颜色映射 + async clear() { + this.colorMap.clear(); + this.usedColors.clear(); + await this.saveToBackend(); + } + + // 更新颜色映射(从现有提示词中收集标签) + updateFromPrompts(prompts) { + if (!Array.isArray(prompts)) return; + + const allTags = new Set(); + + prompts.forEach(prompt => { + if (prompt.tags && Array.isArray(prompt.tags)) { + prompt.tags.forEach(tag => { + if (tag && tag.trim()) { + allTags.add(tag.trim()); + } + }); + } + }); + + // 只为新发现的标签生成颜色 + for (const tag of allTags) { + if (!this.colorMap.has(tag)) { + this.getOrCreateTagColor(tag); + } + } + } + + // 清理未使用的标签 + cleanupUnusedTags(prompts) { + if (!Array.isArray(prompts)) return; + + const usedTags = new Set(); + + // 收集所有正在使用的标签 + prompts.forEach(prompt => { + if (prompt.tags && Array.isArray(prompt.tags)) { + prompt.tags.forEach(tag => { + if (tag && tag.trim()) { + usedTags.add(tag.trim()); + } + }); + } + }); + + // 删除不再使用的标签 + const tagsToDelete = []; + for (const [tagName] of this.colorMap) { + if (!usedTags.has(tagName)) { + tagsToDelete.push(tagName); + } + } + + tagsToDelete.forEach(tagName => { + this.deleteTag(tagName); + }); + + if (tagsToDelete.length > 0) { + console.log(`清理了 ${tagsToDelete.length} 个未使用的标签:`, tagsToDelete); + } + } +} + +// 创建全局单例实例 +export const globalTagColorManager = new TagColorManager(); diff --git a/extensions/Prompt_Manager/styles/components/TagManager.js b/extensions/Prompt_Manager/styles/components/TagManager.js new file mode 100644 index 0000000..1668f5b --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/TagManager.js @@ -0,0 +1,1419 @@ +// 标签管理组件 - 新的气泡交互设计 +// 支持标签气泡点击选择、批量操作等功能 + +import { globalTagColorManager } from './TagColorManager.js'; + +export class TagManager { + constructor(options = {}) { + this.onClose = options.onClose || null; + this.promptManager = options.promptManager || null; + this.onTagColorChange = options.onTagColorChange || null; + + this.container = null; + this.selectedTags = new Set(); // 存储选中的标签名称 + this.createTagManager(); + } + + createTagManager() { + this.container = document.createElement('div'); + this.container.className = 'tag-manager-section'; + // 初始状态:完全隐藏 + this.container.style.display = 'none'; + this.container.innerHTML = ` +
+

+ 🏷️ + 标签管理 +

+ +
+ +
+
+
+ + + +
+
+ 已选择 0 个标签 +
+
+ + +
+
+
+ 📋 + 标签列表 + (0) +
+ + +
+
+
+
+ 🏷️ +

暂无标签

+ 点击"添加标签"按钮来创建您的第一个标签 +
+
+
+
+ + + + + + `; + + this.bindEvents(); + this.addStyles(); + } + + bindEvents() { + // 关闭按钮 + const closeBtn = this.container.querySelector('.tag-manager-close-btn'); + closeBtn.addEventListener('click', () => this.hide()); + + // ESC键关闭 + document.addEventListener('keydown', this.handleKeydown.bind(this)); + + // 操作按钮 + const addBtn = this.container.querySelector('.add-tag-btn'); + const editColorBtn = this.container.querySelector('.edit-color-btn'); + const deleteBtn = this.container.querySelector('.delete-selected-btn'); + + addBtn.addEventListener('click', () => this.showAddModal()); + editColorBtn.addEventListener('click', () => this.showEditColorModal()); + deleteBtn.addEventListener('click', () => this.handleDeleteSelected()); + + // 选择控制按钮 + const selectAllBtn = this.container.querySelector('.select-all-btn'); + const clearSelectionBtn = this.container.querySelector('.clear-selection-btn'); + + selectAllBtn.addEventListener('click', () => this.selectAllTags()); + clearSelectionBtn.addEventListener('click', () => this.clearSelection()); + + // 添加标签弹窗 + this.bindAddModalEvents(); + + // 修改颜色弹窗 + this.bindEditColorModalEvents(); + } bindAddModalEvents() { + const modal = this.container.querySelector('.add-tag-modal'); + const form = modal.querySelector('.add-tag-form'); + const cancelBtn = modal.querySelector('.cancel-btn'); + const overlay = modal.querySelector('.modal-overlay'); + const colorInput = modal.querySelector('.tag-color-input'); + const previewTag = modal.querySelector('#add-preview-tag'); + + form.addEventListener('submit', (e) => { + e.preventDefault(); + this.handleAddTag(); + }); + + cancelBtn.addEventListener('click', () => this.hideAddModal()); + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + this.hideAddModal(); + } + }); + + // 绑定颜色选项点击事件 + const colorOptions = modal.querySelectorAll('.color-option'); + colorOptions.forEach(option => { + option.addEventListener('click', () => { + // 移除其他选项的选中状态 + colorOptions.forEach(opt => opt.classList.remove('selected')); + // 添加当前选项的选中状态 + option.classList.add('selected'); + + const color = option.dataset.color; + colorInput.value = color; + previewTag.style.background = color; + previewTag.style.setProperty('--tag-color', color); + }); + }); + + // 绑定自定义颜色输入变化事件 + colorInput.addEventListener('input', (e) => { + // 移除所有预设颜色的选中状态 + colorOptions.forEach(opt => opt.classList.remove('selected')); + + const color = e.target.value; + previewTag.style.background = color; + previewTag.style.setProperty('--tag-color', color); + }); + + // 初始化预览标签颜色 + previewTag.style.background = colorInput.value; + previewTag.style.setProperty('--tag-color', colorInput.value); + + // 默认选中第一个颜色选项 + if (colorOptions.length > 0) { + colorOptions[0].classList.add('selected'); + } + } bindEditColorModalEvents() { + const modal = this.container.querySelector('.edit-color-modal'); + const form = modal.querySelector('.edit-color-form'); + const cancelBtn = modal.querySelector('.cancel-btn'); + const overlay = modal.querySelector('.modal-overlay'); + const colorInput = modal.querySelector('#edit-tag-color'); + const previewTag = modal.querySelector('#edit-preview-tag'); + + form.addEventListener('submit', (e) => { + e.preventDefault(); + this.handleEditColor(); + }); + + cancelBtn.addEventListener('click', () => this.hideEditColorModal()); + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + this.hideEditColorModal(); + } + }); + + // 绑定颜色选项点击事件 + const colorOptions = modal.querySelectorAll('.color-option'); + colorOptions.forEach(option => { + option.addEventListener('click', () => { + // 移除其他选项的选中状态 + colorOptions.forEach(opt => opt.classList.remove('selected')); + // 添加当前选项的选中状态 + option.classList.add('selected'); + + const color = option.dataset.color; + colorInput.value = color; + previewTag.style.background = color; + previewTag.style.setProperty('--tag-color', color); + + // 更新所有选中标签的预览 + this.updateSelectedTagsPreview(color); + }); + }); + + // 绑定自定义颜色输入变化事件 + colorInput.addEventListener('input', (e) => { + // 移除所有预设颜色的选中状态 + colorOptions.forEach(opt => opt.classList.remove('selected')); + + const color = e.target.value; + previewTag.style.background = color; + previewTag.style.setProperty('--tag-color', color); + + // 更新所有选中标签的预览 + this.updateSelectedTagsPreview(color); + }); + + // 初始化预览标签颜色 + previewTag.style.background = colorInput.value; + previewTag.style.setProperty('--tag-color', colorInput.value); + + // 默认选中第一个颜色选项 + if (colorOptions.length > 0) { + colorOptions[0].classList.add('selected'); + } + } + + handleKeydown(e) { + if (e.key === 'Escape' && this.isVisible()) { + e.preventDefault(); + e.stopPropagation(); + + // 如果有弹窗开启,先关闭弹窗 + const addModal = this.container.querySelector('.add-tag-modal'); + const editModal = this.container.querySelector('.edit-color-modal'); + + if (addModal.style.display !== 'none') { + this.hideAddModal(); + } else if (editModal.style.display !== 'none') { + this.hideEditColorModal(); + } else { + this.hide(); + } + } + } // 显示添加标签弹窗 + showAddModal() { + const modal = this.container.querySelector('.add-tag-modal'); + const nameInput = modal.querySelector('.tag-name-input'); + const colorInput = modal.querySelector('.tag-color-input'); + const previewTag = modal.querySelector('#add-preview-tag'); + const colorOptions = modal.querySelectorAll('.color-option'); + + // 重置表单 + nameInput.value = ''; + const randomColor = this.generateRandomHex(); + colorInput.value = randomColor; + + // 更新预览标签颜色 + previewTag.style.background = randomColor; + previewTag.style.setProperty('--tag-color', randomColor); + + // 清除所有颜色选项的选中状态 + colorOptions.forEach(option => option.classList.remove('selected')); + + // 检查是否有匹配的预设颜色并设置选中状态 + const matchingOption = Array.from(colorOptions).find(option => + option.dataset.color.toLowerCase() === randomColor.toLowerCase() + ); + if (matchingOption) { + matchingOption.classList.add('selected'); + } + modal.style.display = 'block'; + + // 确保预览标签正确显示 - 在下一帧执行以确保DOM已更新 + requestAnimationFrame(() => { + previewTag.style.background = randomColor; + previewTag.style.setProperty('--tag-color', randomColor); + previewTag.style.visibility = 'visible'; + previewTag.style.opacity = '1'; + nameInput.focus(); + }); + }hideAddModal() { + const modal = this.container.querySelector('.add-tag-modal'); + modal.style.display = 'none'; + } + + // 显示修改颜色弹窗 + showEditColorModal() { + if (this.selectedTags.size === 0) return; + + const modal = this.container.querySelector('.edit-color-modal'); + const preview = modal.querySelector('.selected-tags-preview'); + const colorInput = modal.querySelector('#edit-tag-color'); + const previewTag = modal.querySelector('#edit-preview-tag'); + const colorOptions = modal.querySelectorAll('.color-option'); + + // 显示选中的标签预览 + preview.innerHTML = ''; + this.selectedTags.forEach(tagName => { + const color = globalTagColorManager.getTagColor(tagName); + const tagBubble = document.createElement('div'); + tagBubble.className = 'preview-tag-bubble'; + tagBubble.style.setProperty('--tag-color', color); + tagBubble.textContent = tagName; + preview.appendChild(tagBubble); + }); + + // 设置当前颜色(使用第一个选中标签的颜色) + const firstTag = Array.from(this.selectedTags)[0]; + const currentColor = globalTagColorManager.getTagColor(firstTag); + colorInput.value = currentColor; + + // 更新预览标签颜色 + previewTag.style.background = currentColor; + previewTag.style.setProperty('--tag-color', currentColor); + + // 检查是否有匹配的预设颜色并设置选中状态 + colorOptions.forEach(option => option.classList.remove('selected')); + const matchingOption = Array.from(colorOptions).find(option => + option.dataset.color.toLowerCase() === currentColor.toLowerCase() + ); if (matchingOption) { + matchingOption.classList.add('selected'); + } + + modal.style.display = 'block'; + + // 确保预览标签正确显示 - 在下一帧执行以确保DOM已更新 + requestAnimationFrame(() => { + previewTag.style.background = currentColor; + previewTag.style.setProperty('--tag-color', currentColor); + previewTag.style.visibility = 'visible'; + previewTag.style.opacity = '1'; + }); + } + + hideEditColorModal() { + const modal = this.container.querySelector('.edit-color-modal'); + modal.style.display = 'none'; + } + + // 处理添加标签 + handleAddTag() { + const modal = this.container.querySelector('.add-tag-modal'); + const nameInput = modal.querySelector('.tag-name-input'); + const colorInput = modal.querySelector('.tag-color-input'); + + const tagName = nameInput.value.trim(); + const tagColor = colorInput.value; + + if (!tagName) { + alert('请输入标签名称'); + nameInput.focus(); + return; + } + + // 检查标签是否已存在 + if (globalTagColorManager.getAllTags().includes(tagName)) { + alert('标签已存在'); + nameInput.focus(); + return; + } try { + // 添加标签 + globalTagColorManager.setTagColor(tagName, tagColor); + + // 使用统一的提示词管理器进行同步 + if (this.promptManager && this.promptManager.savePrompts) { + this.promptManager.savePrompts(); + } + + // 刷新标签列表 + this.refreshTagsBubbles(); + + // 关闭弹窗 + this.hideAddModal(); + + // 通知颜色变更 + this.notifyTagColorChange(); + + } catch (error) { + console.error('添加标签失败:', error); + alert('添加标签失败: ' + error.message); + } + } + + // 处理修改颜色 + handleEditColor() { + const modal = this.container.querySelector('.edit-color-modal'); + const colorInput = modal.querySelector('#edit-tag-color'); + const newColor = colorInput.value; try { + // 批量修改选中标签的颜色 + this.selectedTags.forEach(tagName => { + globalTagColorManager.setTagColor(tagName, newColor); + }); + + // 使用统一的提示词管理器进行同步 + if (this.promptManager && this.promptManager.savePrompts) { + this.promptManager.savePrompts(); + } + + // 刷新标签列表 + this.refreshTagsBubbles(); + + // 关闭弹窗 + this.hideEditColorModal(); + + // 通知颜色变更 + this.notifyTagColorChange(); + + } catch (error) { + console.error('修改标签颜色失败:', error); + alert('修改标签颜色失败: ' + error.message); + } + } // 处理删除选中标签 + async handleDeleteSelected() { + if (this.selectedTags.size === 0) return; + + const tagNames = Array.from(this.selectedTags).join('、'); + if (confirm(`确定要删除以下标签吗?\n${tagNames}\n\n注意:这些标签将从所有提示词中移除。`)) { + try { + // 从所有提示词中移除这些标签 + if (this.promptManager && this.promptManager.getPrompts) { + const prompts = this.promptManager.getPrompts(); + let hasChanges = false; + + prompts.forEach(prompt => { + if (prompt.tags && Array.isArray(prompt.tags)) { + const originalLength = prompt.tags.length; + prompt.tags = prompt.tags.filter(tag => !this.selectedTags.has(tag)); + if (prompt.tags.length !== originalLength) { + hasChanges = true; + } + } + }); + + // 如果有更改,保存提示词数据 + if (hasChanges) { + this.promptManager.savePrompts(); + console.log('已从提示词中移除删除的标签'); + } + } // 从标签颜色管理器中删除标签 + console.log('准备删除的标签:', Array.from(this.selectedTags)); + this.selectedTags.forEach(tagName => { + console.log(`删除标签: ${tagName}`); + const result = globalTagColorManager.deleteTag(tagName); + console.log(`删除结果: ${result}`); + }); + + // 立即同步标签数据到后端 + console.log('立即同步标签删除到后端...'); + await globalTagColorManager.saveToBackend(); + + // 使用统一的提示词管理器进行同步(与提示词操作保持一致) + if (this.promptManager && this.promptManager.savePrompts) { + console.log('通过提示词管理器同步所有数据到后端...'); + this.promptManager.savePrompts(); + } else { + console.error('提示词管理器不可用,无法同步到后端'); + } + + this.selectedTags.clear(); + this.refreshTagsBubbles(); + this.updateButtonStates(); + + // 通知标签变更,触发所有UI组件刷新 + console.log('通知标签变更,刷新所有UI组件...'); + this.notifyTagColorChange(); + + } catch (error) { + console.error('删除标签失败:', error); + alert('删除标签失败: ' + error.message); + } + } + } + + // 标签气泡点击处理 + handleTagBubbleClick(tagName) { + if (this.selectedTags.has(tagName)) { + // 取消选择 + this.selectedTags.delete(tagName); + } else { + // 选择 + this.selectedTags.add(tagName); + } + + this.updateTagBubbleState(tagName); + this.updateButtonStates(); + this.updateSelectionInfo(); + } + + // 更新标签气泡状态 + updateTagBubbleState(tagName) { + const bubble = this.container.querySelector(`[data-tag="${tagName}"]`); + if (bubble) { + if (this.selectedTags.has(tagName)) { + bubble.classList.add('selected'); + } else { + bubble.classList.remove('selected'); + } + } + } + + // 更新选中标签的预览颜色 + updateSelectedTagsPreview(color) { + const preview = this.container.querySelector('.selected-tags-preview'); + if (preview) { + const tagBubbles = preview.querySelectorAll('.preview-tag-bubble'); + tagBubbles.forEach(bubble => { + bubble.style.background = color; + bubble.style.setProperty('--tag-color', color); + }); + } + } + + // 全选标签 + selectAllTags() { + const allTags = globalTagColorManager.getAllTags(); + allTags.forEach(tagName => { + this.selectedTags.add(tagName); + this.updateTagBubbleState(tagName); + }); + + this.updateButtonStates(); + this.updateSelectionInfo(); + } // 清空选择 + clearSelection() { + // 先清空所有选中状态的视觉反馈 + this.selectedTags.forEach(tagName => { + const bubble = this.container.querySelector(`[data-tag="${tagName}"]`); + if (bubble) { + bubble.classList.remove('selected'); + } + }); + + // 然后清空选中标签集合 + this.selectedTags.clear(); + this.updateButtonStates(); + this.updateSelectionInfo(); + } + + // 更新按钮状态 + updateButtonStates() { + const editColorBtn = this.container.querySelector('.edit-color-btn'); + const deleteBtn = this.container.querySelector('.delete-selected-btn'); + + const hasSelection = this.selectedTags.size > 0; + + editColorBtn.disabled = !hasSelection; + deleteBtn.disabled = !hasSelection; + } + + // 更新选择信息 + updateSelectionInfo() { + const selectedCount = this.container.querySelector('.selected-count'); + selectedCount.textContent = `已选择 ${this.selectedTags.size} 个标签`; + } // 刷新标签气泡列表 + refreshTagsBubbles() { + console.log('TagManager: 开始刷新标签气泡列表...'); + + const container = this.container.querySelector('.tags-bubbles-container'); + const tagsCount = this.container.querySelector('.tags-count'); + const allTags = globalTagColorManager.getAllTags(); + + console.log('TagManager: 从TagColorManager获取到的标签:', allTags); + + // 更新标签计数 + tagsCount.textContent = `(${allTags.length})`; + + if (allTags.length === 0) { + console.log('TagManager: 无标签,显示空状态...'); + container.innerHTML = ` +
+ 🏷️ +

暂无标签

+ 点击"添加标签"按钮来创建您的第一个标签 +
+ `; + return; + } + + // 清理已删除标签的选择状态 + const deletedTags = []; + this.selectedTags.forEach(selectedTag => { + if (!allTags.includes(selectedTag)) { + deletedTags.push(selectedTag); + } + }); + + deletedTags.forEach(tag => { + console.log(`TagManager: 清理已删除标签的选择状态: ${tag}`); + this.selectedTags.delete(tag); + }); + + // 生成标签气泡 + console.log('TagManager: 生成标签气泡...'); + container.innerHTML = ''; + allTags.forEach(tagName => { + const color = globalTagColorManager.getTagColor(tagName); + const bubble = document.createElement('div'); + bubble.className = 'tag-bubble'; + bubble.dataset.tag = tagName; + bubble.style.setProperty('--tag-color', color); + bubble.textContent = tagName; + + // 恢复选中状态 + if (this.selectedTags.has(tagName)) { + bubble.classList.add('selected'); + } + + // 绑定点击事件 + bubble.addEventListener('click', () => { + this.handleTagBubbleClick(tagName); + }); + + container.appendChild(bubble); + }); + // 更新按钮状态和选择信息 + this.updateButtonStates(); + this.updateSelectionInfo(); + + console.log('TagManager: 标签气泡列表刷新完成'); + } + + // 显示/隐藏/可见性检查方法 + show() { + // 查找布局容器 + let layoutContainer = this.container.closest('.prompt-manager-layout'); + if (!layoutContainer) { + const formSection = this.container.parentNode?.closest('.prompt-manager-form'); + if (formSection) { + layoutContainer = formSection.closest('.prompt-manager-layout'); + } + } + + if (layoutContainer) { + layoutContainer.classList.add('form-expanded'); + } + + // 显示标签管理器容器 + this.container.style.display = 'flex'; + this.container.classList.add('active'); + + // 等待下一帧确保DOM更新 + requestAnimationFrame(() => { + // 刷新标签列表 + this.refreshTagsBubbles(); + this.updateButtonStates(); + this.updateSelectionInfo(); + }); + } + + hide() { + // 隐藏标签管理器容器 + this.container.classList.remove('active'); + this.container.style.display = 'none'; + + // 清空选择 + this.selectedTags.clear(); + + // 查找布局容器,检查是否还有其他内容需要显示 + const layoutContainer = this.container.closest('.prompt-manager-layout'); + if (layoutContainer) { + const formSection = layoutContainer.querySelector('.prompt-manager-form'); + const promptForm = formSection?.querySelector('.prompt-form-section'); + const isPromptFormVisible = promptForm && promptForm.style.visibility !== 'hidden'; + + // 只有当没有其他内容显示时才移除展开类 + if (!isPromptFormVisible) { + layoutContainer.classList.remove('form-expanded'); + } + } + // 调用关闭回调 + if (this.onClose) { + this.onClose(); + } + + // 通知标签变更(确保搜索栏能获取到最新的标签列表) + this.notifyTagColorChange(); + } + + isVisible() { + return this.container.classList.contains('active') && + this.container.style.display !== 'none'; + } + + getElement() { + return this.container; + } + + destroy() { + if (this.isVisible()) { + this.hide(); + } + + if (this.container && this.container.parentNode) { + this.container.parentNode.removeChild(this.container); + } + + // 移除事件监听器 + document.removeEventListener('keydown', this.handleKeydown.bind(this)); + } + + // 工具方法 + generateRandomHex() { + const hue = Math.random() * 360; + const saturation = 0.6 + Math.random() * 0.3; // 60%-90% + const lightness = 0.4 + Math.random() * 0.2; // 40%-60% + + const c = (1 - Math.abs(2 * lightness - 1)) * saturation; + const x = c * (1 - Math.abs((hue / 60) % 2 - 1)); + const m = lightness - c / 2; + + let r, g, b; + + if (hue < 60) { + r = c; g = x; b = 0; + } else if (hue < 120) { + r = x; g = c; b = 0; + } else if (hue < 180) { + r = 0; g = c; b = x; + } else if (hue < 240) { + r = 0; g = x; b = c; + } else if (hue < 300) { + r = x; g = 0; b = c; + } else { + r = c; g = 0; b = x; + } + + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + + return "#" + [r, g, b].map(x => { + const hex = x.toString(16); + return hex.length === 1 ? "0" + hex : hex; + }).join(""); + } + + notifyTagColorChange() { + if (this.onTagColorChange) { + this.onTagColorChange(); + } + } + + addStyles() { + const style = document.createElement('style'); + style.textContent = ` + /* ========== 标签管理器主容器 ========== */ + .tag-manager-section { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: transparent; + display: none; + flex-direction: column; + overflow: hidden; + z-index: 2; + } + + .tag-manager-section.active { + display: flex; + } + + /* ========== 标签管理器头部 ========== */ + .tag-manager-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background: linear-gradient(135deg, #2a2a2d, #232326); + position: relative; + flex-shrink: 0; + } + + .tag-manager-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + } + + .tag-manager-title { + margin: 0; + color: #ffffff; + font-size: 18px; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + letter-spacing: 0.5px; + } + + .title-icon { + font-size: 20px; + opacity: 0.9; + } + + .tag-manager-close-btn { + background: none; + border: none; + color: #888; + font-size: 18px; + cursor: pointer; + padding: 8px; + border-radius: 6px; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + } + + .tag-manager-close-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); + } /* ========== 标签管理器主体 ========== */ + .tag-manager-body { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 20px; + gap: 20px; + min-height: 0; /* 确保flex子项能正确收缩 */ + } /* ========== 操作按钮区域 ========== */ + .tag-actions-section { + background: linear-gradient(145deg, #1e1e21, #2a2a2d); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 12px; + flex-shrink: 0; + }.action-buttons { + display: flex; + gap: 8px; + margin-bottom: 12px; + flex-wrap: wrap; + } + + .action-btn { + background: linear-gradient(145deg, #3a3a3d, #2d2d30); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 8px; + color: #ffffff; + padding: 8px 12px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 500; + flex: 1; + justify-content: center; + min-width: 0; + white-space: nowrap; + } + + .action-btn:hover:not(:disabled) { + background: linear-gradient(145deg, #4a4a4d, #3d3d40); + border-color: rgba(255, 255, 255, 0.25); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + background: linear-gradient(145deg, #2a2a2d, #1e1e21); + border-color: rgba(255, 255, 255, 0.05); + } .action-btn .btn-icon { + font-size: 14px; + flex-shrink: 0; + } + + .action-btn .btn-text { + flex-shrink: 1; + overflow: hidden; + text-overflow: ellipsis; + } + + .selection-info { + text-align: center; + color: #ccc; + font-size: 13px; + } /* ========== 标签气泡区域 ========== */ + .tags-bubbles-section { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background: linear-gradient(145deg, #1e1e21, #2a2a2d); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + min-height: 0; /* 确保flex子项能正确收缩 */ + } .section-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + flex-shrink: 0; + min-height: 56px; /* 确保有足够的高度避免按钮重叠 */ + } + + .section-title { + margin: 0; + color: #e0e0e0; + font-size: 15px; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; + } .section-actions { + display: flex; + gap: 8px; + flex-shrink: 0; + align-items: center; + } .select-all-btn, .clear-selection-btn { + background: linear-gradient(145deg, #3a3a3d, #2d2d30); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + color: #ffffff; + padding: 6px 12px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 12px; + white-space: nowrap; + min-width: 80px; + text-align: center; + } + + .select-all-btn:hover, .clear-selection-btn:hover { + background: linear-gradient(145deg, #4a4a4d, #3d3d40); + border-color: rgba(255, 255, 255, 0.25); + } .tags-bubbles-container { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-wrap: wrap; + gap: 10px; + align-content: flex-start; + align-items: flex-start; + justify-content: flex-start; + min-height: 0; + }.tag-bubble { + background: var(--tag-color, #2a82e4); + color: white; + padding: 8px 16px; + border-radius: 20px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; + user-select: none; + position: relative; + overflow: hidden; + letter-spacing: 0.25px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + text-align: center; + border: 1px solid rgba(255, 255, 255, 0.1); + transform: scale(1); + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + } .tag-bubble::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent); + border-radius: inherit; + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + } + + .tag-bubble:hover { + box-shadow: + 0 0 10px rgba(255, 255, 255, 0.3), + 0 4px 15px rgba(0, 0, 0, 0.2); + transform: scale(1.05); + border-color: rgba(255, 255, 255, 0.4); + filter: brightness(1.1) saturate(1.05); + } + + .tag-bubble:hover::before { + opacity: 1; + } .tag-bubble.selected { + border-color: #ffffff; + box-shadow: + 0 0 0 2px rgba(255, 255, 255, 0.6), + 0 0 15px rgba(255, 255, 255, 0.4), + 0 4px 20px rgba(0, 0, 0, 0.3); + transform: scale(1.02); + filter: brightness(1.15) saturate(1.1); + } + + .tag-bubble.selected::before { + opacity: 1; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05)); + } + + .no-tags-message { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + width: 100%; + height: 100%; + color: #888; + gap: 12px; + } + + .empty-icon { + font-size: 48px; + opacity: 0.6; + } + + .no-tags-message p { + margin: 0; + font-size: 16px; + font-weight: 500; + } + + .no-tags-message small { + font-size: 13px; + opacity: 0.8; + } + + /* ========== 弹窗样式 ========== */ + .add-tag-modal, .edit-color-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10000; + display: none; + } + + .modal-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(4px); + } + + .modal-content { + background: linear-gradient(145deg, #2d2d30, #252528); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + padding: 24px; + max-width: 400px; + width: 90%; + box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4); + } + + .modal-title { + margin: 0 0 20px 0; + color: #ffffff; + font-size: 18px; + font-weight: 600; + text-align: center; + } + + .form-group { + margin-bottom: 16px; + } + + .form-group label { + display: block; + color: #e0e0e0; + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; + } + + .form-group input { + width: 100%; + background: linear-gradient(145deg, #141417, #1a1a1d); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 8px; + color: #ffffff; + padding: 12px 16px; + font-size: 14px; + transition: all 0.3s ease; + box-sizing: border-box; + } + + .form-group input:focus { + outline: none; + border-color: #2a82e4; + box-shadow: 0 0 0 2px rgba(42, 130, 228, 0.2); + } .form-actions { + display: flex; + gap: 12px; + margin-top: 24px; + }.confirm-btn, .cancel-btn { + flex: 1; + padding: 12px 20px; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; + text-align: center; + } + + .confirm-btn { + background: linear-gradient(145deg, #2a82e4, #1e5fb8); + color: white; + } + + .confirm-btn:hover { + background: linear-gradient(145deg, #3a92f4, #2e6fc8); + transform: translateY(-1px); + } + + .cancel-btn { + background: linear-gradient(145deg, #3a3a3d, #2d2d30); + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.15); + } + + .cancel-btn:hover { + background: linear-gradient(145deg, #4a4a4d, #3d3d40); + border-color: rgba(255, 255, 255, 0.25); + } + + .selected-tags-preview { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; + padding: 12px; + background: rgba(0, 0, 0, 0.2); + border-radius: 8px; + } .preview-tag-bubble { + background: var(--tag-color, #2a82e4); + color: white; + padding: 6px 12px; + border-radius: 16px; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.25px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); + text-align: center; + border: 1px solid rgba(255, 255, 255, 0.1); + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000; + } /* ========== 调色板样式 ========== */ + .color-palette-container { + display: flex; + flex-direction: column; + gap: 12px; + } + + .preset-colors { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 8px; + padding: 12px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .color-option { + width: 32px; + height: 32px; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; + position: relative; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); + } + + .color-option:hover { + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + } + + .color-option.selected { + border-color: #ffffff; + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.3); + transform: scale(1.1); + } + + .color-option.selected::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #ffffff; + font-size: 14px; + font-weight: bold; + text-shadow: 0 0 3px rgba(0, 0, 0, 0.8); + } + + .custom-color-section { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .custom-color-section label { + margin: 0 !important; + font-size: 13px; + color: #ccc; + white-space: nowrap; + } + + .custom-color-section .tag-color-input { + width: 40px !important; + height: 32px !important; + padding: 0 !important; + border: 2px solid rgba(255, 255, 255, 0.2) !important; + border-radius: 6px !important; + cursor: pointer; + background: none !important; + } + + .custom-color-section .tag-color-input:hover { + border-color: rgba(255, 255, 255, 0.4) !important; + transform: scale(1.05); + } .color-preview { + display: flex; + justify-content: center; + align-items: center; + padding: 12px; + background: linear-gradient(145deg, #1a1a1a, #252525); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + min-height: 60px; + width: 100%; + box-sizing: border-box; + } .preview-tag { + background: var(--tag-color, #2a82e4) !important; + color: white !important; + padding: 8px 16px !important; + border-radius: 16px !important; + font-size: 13px !important; + font-weight: 500 !important; + letter-spacing: 0.25px !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important; + text-align: center !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 -1px 0 #000, + 0 1px 0 #000, + -1px 0 0 #000, + 1px 0 0 #000 !important; + transition: all 0.3s ease !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + white-space: nowrap !important; + margin: 0 !important; + position: relative !important; + z-index: 1 !important; + opacity: 1 !important; + visibility: visible !important; + line-height: 1 !important; + vertical-align: middle !important; + } + + /* ========== 滚动条样式 ========== */ + .tags-bubbles-container::-webkit-scrollbar { + width: 6px; + } + + .tags-bubbles-container::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 3px; + } + + .tags-bubbles-container::-webkit-scrollbar-thumb { + background: linear-gradient(145deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.1)); + border-radius: 3px; + border: 1px solid rgba(255, 255, 255, 0.05); + } + + .tags-bubbles-container::-webkit-scrollbar-thumb:hover { + background: linear-gradient(145deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.2)); + } + `; + + if (!document.head.querySelector('style[data-component="tag-manager-new"]')) { + style.setAttribute('data-component', 'tag-manager-new'); + document.head.appendChild(style); + } + } +} diff --git a/extensions/Prompt_Manager/styles/components/index.js b/extensions/Prompt_Manager/styles/components/index.js new file mode 100644 index 0000000..9596ae2 --- /dev/null +++ b/extensions/Prompt_Manager/styles/components/index.js @@ -0,0 +1,80 @@ +// 组件统一导出文件 +// 方便在其他地方统一导入所有UI组件 + +export { MainModal } from './MainModal.js'; +export { SearchBar } from './SearchBar.js'; +export { PromptList } from './PromptList.js'; +export { PromptForm } from './PromptForm.js'; +export { DialogComponents } from './DialogComponents.js'; +export { TagColorManager, globalTagColorManager } from './TagColorManager.js'; +export { TagManager } from './TagManager.js'; + +// 组件版本信息 +export const COMPONENT_VERSION = '1.0.0'; + +// 组件元数据 +export const COMPONENT_METADATA = { + MainModal: { + version: '1.0.0', + description: '主模态框组件,负责整体模态框结构和基础样式', + dependencies: [] + }, + SearchBar: { + version: '1.0.0', + description: '搜索栏组件,负责提示词搜索功能', + dependencies: [] + }, + PromptList: { + version: '1.0.0', + description: '列表组件,负责提示词列表的显示和交互', + dependencies: [] + }, + PromptForm: { + version: '1.0.0', + description: '表单组件,负责添加/编辑提示词的表单', + dependencies: [] + }, + DialogComponents: { + version: '1.0.0', + description: '对话框组件,负责各种确认对话框和选择器', + dependencies: [] + } +}; + +// 验证所有组件是否正确加载 +export function validateComponents() { + const components = [ + 'MainModal', + 'SearchBar', + 'PromptList', + 'PromptForm', + 'DialogComponents' + ]; + + const results = {}; + let allValid = true; + + components.forEach(componentName => { + try { + const Component = eval(componentName); + results[componentName] = { + loaded: true, + constructor: typeof Component === 'function', + metadata: COMPONENT_METADATA[componentName] + }; + } catch (error) { + results[componentName] = { + loaded: false, + error: error.message, + metadata: COMPONENT_METADATA[componentName] + }; + allValid = false; + } + }); + + return { + allValid, + components: results, + version: COMPONENT_VERSION + }; +} diff --git a/pyproject.toml b/pyproject.toml index 0de7cf7..b30f3a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-dd-nodes" description = "Advanced utility nodes for ComfyUI with intelligent layout and animation features." -version = "2.5.0" +version = "2.6.0" license = {file = "LICENSE"} dependencies = ["git+https://github.com/Dontdrunk/ComfyUI-DD-Nodes.git"]