您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为 DeepSeek 添加提示词管理功能。全新Linear设计风格,TailwindCSS构建
// ==UserScript== // @name 给DeepSeek添加原生化的提示词管理功能 // @namespace http://tampermonkey.net/ // @version 1.8.9 // @description 为 DeepSeek 添加提示词管理功能。全新Linear设计风格,TailwindCSS构建 // @author gpt // @match https://chat.deepseek.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Script Configuration --- const SCRIPT_PREFIX = 'dspm_'; // Prefix for localStorage keys, IDs, and CSS classes // --- Helper Functions --- const $ = (selector, parent = document) => parent.querySelector(selector); const $$ = (selector, parent = document) => parent.querySelectorAll(selector); const debounce = (fn, delay) => { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }; // --- Dynamic CDN Injection --- function injectCDN(url, type) { return new Promise((resolve, reject) => { let element; if (type === 'script') { element = document.createElement('script'); element.src = url; } else if (type === 'link') { element = document.createElement('link'); element.href = url; element.rel = 'stylesheet'; } if (element) { element.onload = resolve; element.onerror = (err) => { console.error(`DSPM: Failed to load CDN ${type}: ${url}`, err); reject(err); }; document.head.appendChild(element); } else { reject(new Error('Invalid CDN type')); } }); } // --- Theme Management --- const applyTheme = (theme) => { console.log(`DSPM: Applying theme: ${theme}`); if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } GM_setValue(SCRIPT_PREFIX + 'theme', theme); }; const getSavedTheme = () => GM_getValue(SCRIPT_PREFIX + 'theme', 'system'); const initTheme = () => { const savedTheme = getSavedTheme(); console.log(`DSPM: Initializing theme. Saved: ${savedTheme}`); if (savedTheme === 'system') { const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; console.log(`DSPM: System prefers dark: ${systemPrefersDark}`); applyTheme(systemPrefersDark ? 'dark' : 'light'); } else { applyTheme(savedTheme); } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { console.log(`DSPM: System theme changed. Prefers dark: ${e.matches}`); if (getSavedTheme() === 'system') { applyTheme(e.matches ? 'dark' : 'light'); } }); }; // --- Base Styles (Minimal, mostly for scrollbars and transitions) --- GM_addStyle(` .dspm-smooth-scroll { scroll-behavior: smooth; } .dspm-custom-scrollbar::-webkit-scrollbar { width: 8px; height: 8px; } .dspm-custom-scrollbar::-webkit-scrollbar-track { background: transparent; } .dspm-custom-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(156, 163, 175, 0.5); /* Light mode thumb color */ border-radius: 4px; } .dspm-custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: rgba(107, 114, 128, 0.7); } .dark .dspm-custom-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(75, 85, 99, 0.6); /* Dark mode thumb color */ } .dark .dspm-custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: rgba(55, 65, 81, 0.8); } .dspm-fade-in { animation: dspmFadeInAnimation 0.3s ease-out forwards; opacity: 0; } @keyframes dspmFadeInAnimation { to { opacity: 1; } } /* Management Panel Styles */ .dspm-management-side-panel { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); transform: translateX(100%); height: 100vh !important; /* Ensure panel is full viewport height */ } .dspm-management-side-panel.visible { transform: translateX(0); } /* Content area within the management panel */ .dspm-management-content-area { height: calc(100vh - 62px) !important; /* Full height minus header (assuming header is 62px) */ overflow-y: scroll !important; /* Ensure vertical scrollbar track is always visible */ } .dspm-management-panel-toggle-btn { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.2s, color 0.2s; } .dspm-management-panel-toggle-btn.panel-visible { transform: translateX(-340px); /* Adjust if panel width changes */ } body { transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out; } `); // --- UI Element Variables --- let usagePromptDropdown; let promptUsageButtonWrapper; let promptUsageButton; let managementSidePanel; let managementPanelToggleButton; let themeToggleButton; // --- Core Functions --- function createElements() { const buttonContainer = $('.ec4f5d61'); if (!buttonContainer || $(`#${SCRIPT_PREFIX}usage_button`)) return; promptUsageButtonWrapper = document.createElement('div'); promptUsageButtonWrapper.className = 'prompt-usage-button-wrapper inline-flex items-center relative'; promptUsageButton = document.createElement('button'); promptUsageButton.id = SCRIPT_PREFIX + 'usage_button'; promptUsageButton.className = `prompt-usage-button flex items-center justify-center h-[34px] px-[14px] py-[8px] text-[14px] font-medium bg-white dark:bg-gray-800 text-[#4c4c4c] dark:text-gray-300 border border-[rgba(0,0,0,0.12)] dark:border-gray-600 rounded-[10px] hover:bg-[#E0E4ED] dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 transition-all duration-150 ease-in-out transform hover:scale-105 ml-1 mr-2.5`; promptUsageButton.innerHTML = `<i class="fas fa-star w-[18px] h-[18px] mr-2"></i><span class="button-text-content">提示词库</span>`; promptUsageButtonWrapper.appendChild(promptUsageButton); const buttonsInContainer = buttonContainer.children; if (buttonsInContainer.length >= 1) buttonContainer.insertBefore(promptUsageButtonWrapper, buttonsInContainer[1]); else buttonContainer.appendChild(promptUsageButtonWrapper); usagePromptDropdown = document.createElement('div'); usagePromptDropdown.id = SCRIPT_PREFIX + 'usage_dropdown'; usagePromptDropdown.className = `usage-prompt-dropdown dspm-custom-scrollbar dspm-fade-in hidden absolute z-50 mt-2 w-72 md:w-80 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-xl max-h-80 overflow-y-auto p-2 space-y-1`; // Dropdown uses overflow-y: auto document.body.appendChild(usagePromptDropdown); promptUsageButton.addEventListener('click', (e) => { e.stopPropagation(); const isVisible = !usagePromptDropdown.classList.contains('hidden'); if (isVisible) closeUsageDropdown(); else openUsageDropdown(); }); managementPanelToggleButton = document.createElement('button'); managementPanelToggleButton.id = SCRIPT_PREFIX + 'management_toggle_btn'; managementPanelToggleButton.className = `dspm-management-panel-toggle-btn fixed top-1/2 -translate-y-1/2 right-0 z-40 p-2.5 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white border border-r-0 border-gray-300 dark:border-gray-600 rounded-l-md shadow-md focus:outline-none transition-all duration-150 ease-in-out transform hover:scale-105`; managementPanelToggleButton.title = '管理提示词'; managementPanelToggleButton.innerHTML = `<i class="fas fa-cog fa-lg"></i>`; document.body.appendChild(managementPanelToggleButton); managementPanelToggleButton.addEventListener('click', (e) => { e.stopPropagation(); toggleManagementPanel(); }); managementSidePanel = document.createElement('div'); managementSidePanel.id = SCRIPT_PREFIX + 'management_side_panel'; managementSidePanel.className = `dspm-management-side-panel fixed top-0 right-0 h-full w-full max-w-sm md:max-w-md z-50 bg-gray-50 dark:bg-gray-850 shadow-2xl flex flex-col border-l border-gray-200 dark:border-gray-700`; document.body.appendChild(managementSidePanel); document.addEventListener('click', (e) => { if (document.body.classList.contains(SCRIPT_PREFIX + 'modal-open')) { // If a modal is open, its own overlay click handler should manage closure. // This global listener should not interfere with modal overlay clicks. // The check e.target.id for overlays was removed here as it's redundant // with the modal's own specific overlay click handlers. return; } if (usagePromptDropdown && !usagePromptDropdown.classList.contains('hidden')) { if (!usagePromptDropdown.contains(e.target) && !promptUsageButton.contains(e.target)) { closeUsageDropdown(); } } if (managementSidePanel && managementSidePanel.classList.contains('visible')) { const importExportMenu = $(`#${SCRIPT_PREFIX}import_export_menu`); if (!managementSidePanel.contains(e.target) && !managementPanelToggleButton.contains(e.target) && !(importExportMenu && importExportMenu.contains(e.target))) { toggleManagementPanel(false); } } }); } function openUsageDropdown() { if (!usagePromptDropdown || !promptUsageButton) return; updateUsageDropdown(); usagePromptDropdown.classList.remove('hidden'); promptUsageButton.classList.add('active', 'bg-[#E0E4ED]', 'dark:bg-gray-700'); updateUsageDropdownPosition(); } function closeUsageDropdown() { if (!usagePromptDropdown || !promptUsageButton) return; usagePromptDropdown.classList.add('hidden'); promptUsageButton.classList.remove('active', 'bg-[#E0E4ED]', 'dark:bg-gray-700'); } function updateUsageDropdownPosition() { if (!usagePromptDropdown || !promptUsageButtonWrapper) return; const buttonRect = promptUsageButtonWrapper.getBoundingClientRect(); const dropdownHeight = usagePromptDropdown.offsetHeight; const windowHeight = window.innerHeight; let top = buttonRect.bottom + 8; if (top + dropdownHeight > windowHeight - 20) top = buttonRect.top - dropdownHeight - 8; if (top < 20) top = 20; usagePromptDropdown.style.left = `${buttonRect.left}px`; usagePromptDropdown.style.top = `${top}px`; usagePromptDropdown.style.minWidth = `${buttonRect.width}px`; } function updateUsageDropdown() { if (!usagePromptDropdown) return; const savedPrompts = GM_getValue(SCRIPT_PREFIX + 'prompts', []); usagePromptDropdown.innerHTML = ''; if (savedPrompts.length === 0) { usagePromptDropdown.innerHTML = `<div class="p-4 text-center text-sm text-gray-500 dark:text-gray-400">暂无提示词。请通过右侧 <i class="fas fa-cog mx-1"></i> 管理面板添加。</div>`; return; } savedPrompts.forEach(prompt => { const item = document.createElement('div'); item.className = `p-2.5 rounded-md cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150`; item.innerHTML = `<div class="font-semibold text-sm text-gray-800 dark:text-gray-200 truncate">${prompt.title}</div><div class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5">${prompt.content}</div>`; item.addEventListener('click', async () => { try { const textArea = $('#chat-input'); if (!textArea) throw new Error('Chat input not found'); textArea.focus(); document.execCommand('selectAll', false, null); document.execCommand('delete', false, null); document.execCommand('insertText', false, prompt.content); closeUsageDropdown(); } catch (err) { console.error('DSPM: Failed to fill prompt:', err); alert('填充提示词失败。'); } }); usagePromptDropdown.appendChild(item); }); } function toggleManagementPanel(forceState) { if (!managementSidePanel || !managementPanelToggleButton) return; const show = forceState !== undefined ? forceState : !managementSidePanel.classList.contains('visible'); if (show) { updateManagementPanelContent(); managementSidePanel.classList.add('visible'); managementPanelToggleButton.classList.add('panel-visible'); } else { managementSidePanel.classList.remove('visible'); managementPanelToggleButton.classList.remove('panel-visible'); } } function updateManagementPanelContent() { if (!managementSidePanel) return; const savedPrompts = GM_getValue(SCRIPT_PREFIX + 'prompts', []); const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; managementSidePanel.innerHTML = ` <div class="flex flex-col h-full"> <div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 sticky top-0 z-10"> <h3 class="text-lg font-semibold text-gray-900 dark:text-white">管理提示词</h3> <div class="flex items-center space-x-1"> <button id="${SCRIPT_PREFIX}theme_toggle_btn" title="切换主题" class="p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"> <i class="fas ${currentTheme === 'dark' ? 'fa-sun' : 'fa-moon'} fa-fw"></i> </button> <button id="${SCRIPT_PREFIX}import_export_btn" title="导入/导出" class="p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"> <i class="fas fa-file-import fa-fw"></i> </button> <button id="${SCRIPT_PREFIX}clear_all_btn" title="清空全部提示词" class="p-2 rounded-md text-red-500 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-700/30 hover:text-red-700 dark:hover:text-red-300 transition-colors"> <i class="fas fa-trash-alt fa-fw"></i> </button> <button id="${SCRIPT_PREFIX}add_prompt_btn" class="px-3 py-2 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 transition-all duration-150 ease-in-out transform hover:scale-105"> <i class="fas fa-plus mr-1.5"></i> 创建 </button> </div> </div> <div class="dspm-management-content-area flex-grow p-4 dspm-custom-scrollbar dspm-smooth-scroll space-y-3"> ${savedPrompts.length === 0 ? `<div class="text-center py-10"> <i class="fas fa-folder-open fa-3x text-gray-400 dark:text-gray-500 mb-3"></i> <p class="text-sm text-gray-500 dark:text-gray-400">暂无提示词,点击“创建”添加一个吧!</p> </div>` : savedPrompts.map((prompt, index) => ` <div id="${SCRIPT_PREFIX}prompt_card_${index}" class="dspm-prompt-card bg-white dark:bg-gray-750 p-3.5 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-md hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-150 ease-in-out"> <div class="flex items-center justify-between"> <h4 class="text-md font-semibold text-gray-800 dark:text-gray-100 truncate pr-2 flex-grow">${prompt.title}</h4> <button data-index="${index}" class="dspm-edit-prompt-btn p-1.5 rounded-md text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"> <i class="fas fa-pen fa-sm"></i> </button> </div> <p class="text-sm text-gray-600 dark:text-gray-300 mt-1.5 break-words line-clamp-2">${prompt.content}</p> </div>`).join('') } </div> </div>`; themeToggleButton = $(`#${SCRIPT_PREFIX}theme_toggle_btn`, managementSidePanel); themeToggleButton.addEventListener('click', (e) => { e.stopPropagation(); console.log('DSPM: Theme toggle clicked'); const newTheme = document.documentElement.classList.contains('dark') ? 'light' : 'dark'; applyTheme(newTheme); themeToggleButton.innerHTML = `<i class="fas ${newTheme === 'dark' ? 'fa-sun' : 'fa-moon'} fa-fw"></i>`; }); $(`#${SCRIPT_PREFIX}add_prompt_btn`, managementSidePanel).addEventListener('click', (e) => { e.stopPropagation(); showEditPromptModal(); }); $$('.dspm-edit-prompt-btn', managementSidePanel).forEach(button => { button.addEventListener('click', function(e) { e.stopPropagation(); showEditPromptModal(savedPrompts[parseInt(this.dataset.index)], parseInt(this.dataset.index)); }); }); setupImportExportMenu($(`#${SCRIPT_PREFIX}import_export_btn`, managementSidePanel)); const clearAllBtn = $(`#${SCRIPT_PREFIX}clear_all_btn`, managementSidePanel); if (clearAllBtn) { clearAllBtn.addEventListener('click', (e) => { e.stopPropagation(); showConfirmationDialog( '确认清空全部提示词', '确定要删除所有已保存的提示词吗?此操作无法撤销。', () => { console.log('DSPM: Clearing all prompts.'); GM_setValue(SCRIPT_PREFIX + 'prompts', []); updateManagementPanelContent(); updateUsageDropdown(); alert('所有提示词已清空!'); }, '清空全部' ); }); } } function showEditPromptModal(prompt = null, index = null) { const isEditing = prompt !== null; const modalId = SCRIPT_PREFIX + 'edit_modal_overlay'; if ($(`#${modalId}`)) $(`#${modalId}`).remove(); const modalOverlay = document.createElement('div'); modalOverlay.id = modalId; modalOverlay.className = `fixed inset-0 z-[100] flex items-center justify-center p-4 bg-gray-900 bg-opacity-60 dark:bg-opacity-75 dspm-fade-in`; modalOverlay.innerHTML = `<div class="bg-white dark:bg-gray-800 bg-opacity-100 dark:bg-opacity-100 rounded-lg shadow-xl w-full max-w-lg p-6 space-y-4 transform transition-all duration-300 ease-out scale-95 opacity-0" role="dialog" aria-modal="true"> <div class="flex items-center justify-between pb-3 border-b border-gray-200 dark:border-gray-700"><h3 class="text-xl font-semibold text-gray-900 dark:text-white">${isEditing ? '编辑指令' : '创建新指令'}</h3>${isEditing ? `<button id="${SCRIPT_PREFIX}delete_prompt_btn" class="px-3 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-900/50 hover:bg-red-200 dark:hover:bg-red-800/70 rounded-md transition-colors"><i class="fas fa-trash-alt mr-1"></i> 删除</button>` : ''}<button id="${SCRIPT_PREFIX}close_modal_btn" class="p-1.5 rounded-md text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"><i class="fas fa-times fa-lg"></i></button></div> <div class="space-y-4"><div><label for="${SCRIPT_PREFIX}prompt_title" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">指令标题 <span class="text-red-500">*</span></label><input type="text" id="${SCRIPT_PREFIX}prompt_title" value="${isEditing ? prompt.title : ''}" placeholder="例如:周报小助手" class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"></div><div><label for="${SCRIPT_PREFIX}prompt_content" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">指令内容 <span class="text-red-500">*</span></label><textarea id="${SCRIPT_PREFIX}prompt_content" rows="6" placeholder="请输入希望AI执行的具体指令内容..." class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dspm-custom-scrollbar">${isEditing ? prompt.content : ''}</textarea></div></div> <div class="pt-4 flex justify-end space-x-3 border-t border-gray-200 dark:border-gray-700"><button id="${SCRIPT_PREFIX}cancel_modal_btn" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">取消</button><button id="${SCRIPT_PREFIX}save_prompt_btn" class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 transition-all duration-150 ease-in-out transform hover:scale-105">${isEditing ? '保存修改' : '创建指令'}</button></div></div>`; document.body.appendChild(modalOverlay); document.body.classList.add(SCRIPT_PREFIX + 'modal-open'); setTimeout(() => { const modalContent = $('div[role="dialog"]', modalOverlay); if (modalContent) { modalContent.classList.remove('scale-95', 'opacity-0'); modalContent.classList.add('scale-100', 'opacity-100'); } }, 10); const closeModal = () => { const modalContent = $('div[role="dialog"]', modalOverlay); if (modalContent) { modalContent.classList.add('scale-95', 'opacity-0'); modalContent.classList.remove('scale-100', 'opacity-100'); setTimeout(() => { if (modalOverlay) modalOverlay.remove(); // Check if modalOverlay still exists document.body.classList.remove(SCRIPT_PREFIX + 'modal-open'); }, 300); } else { if (modalOverlay) modalOverlay.remove(); // Check if modalOverlay still exists document.body.classList.remove(SCRIPT_PREFIX + 'modal-open'); } }; $(`#${SCRIPT_PREFIX}close_modal_btn`, modalOverlay).onclick = closeModal; $(`#${SCRIPT_PREFIX}cancel_modal_btn`, modalOverlay).onclick = closeModal; // Diagnostic log for edit/create modal overlay click modalOverlay.onclick = (e) => { console.log('DSPM: Edit Modal Overlay clicked. Target:', e.target, 'Expected Overlay:', modalOverlay); if (e.target === modalOverlay) { console.log('DSPM: Closing Edit Modal due to overlay click.'); closeModal(); } else { console.log('DSPM: Edit Modal Overlay click ignored, target was not overlay itself.'); } }; if (!isEditing && $('#chat-input')?.value.trim()) $(`#${SCRIPT_PREFIX}prompt_content`, modalOverlay).value = $('#chat-input').value.trim(); $(`#${SCRIPT_PREFIX}prompt_title`, modalOverlay).focus(); if (isEditing) { $(`#${SCRIPT_PREFIX}delete_prompt_btn`, modalOverlay).onclick = () => { showConfirmationDialog('确认删除', `确定要删除指令 “<strong>${prompt.title}</strong>” 吗?此操作无法撤销。`, () => { const prompts = GM_getValue(SCRIPT_PREFIX + 'prompts', []); prompts.splice(index, 1); GM_setValue(SCRIPT_PREFIX + 'prompts', prompts); closeModal(); updateManagementPanelContent(); updateUsageDropdown(); }); }; } $(`#${SCRIPT_PREFIX}save_prompt_btn`, modalOverlay).onclick = () => { const title = $(`#${SCRIPT_PREFIX}prompt_title`, modalOverlay).value.trim(); const content = $(`#${SCRIPT_PREFIX}prompt_content`, modalOverlay).value.trim(); if (!title || !content) { alert(title ? '指令内容不能为空!' : '指令标题不能为空!'); (title ? $(`#${SCRIPT_PREFIX}prompt_content`, modalOverlay) : $(`#${SCRIPT_PREFIX}prompt_title`, modalOverlay)).focus(); return; } const prompts = GM_getValue(SCRIPT_PREFIX + 'prompts', []); if (isEditing) prompts[index] = { title, content }; else prompts.push({ title, content }); GM_setValue(SCRIPT_PREFIX + 'prompts', prompts); closeModal(); updateManagementPanelContent(); updateUsageDropdown(); }; } function showConfirmationDialog(title, message, onConfirm, confirmButtonText = '确认删除') { const dialogId = SCRIPT_PREFIX + 'confirm_dialog_overlay'; if ($(`#${dialogId}`)) $(`#${dialogId}`).remove(); const dialogOverlay = document.createElement('div'); dialogOverlay.id = dialogId; dialogOverlay.className = `fixed inset-0 z-[110] flex items-center justify-center p-4 bg-gray-900 bg-opacity-70 dark:bg-opacity-80 dspm-fade-in`; dialogOverlay.innerHTML = `<div class="bg-white dark:bg-gray-800 bg-opacity-100 dark:bg-opacity-100 rounded-lg shadow-xl w-full max-w-md p-6 space-y-4 transform transition-all duration-300 ease-out scale-95 opacity-0"><h3 class="text-lg font-medium text-gray-900 dark:text-white">${title}</h3><p class="text-sm text-gray-600 dark:text-gray-300">${message}</p><div class="flex justify-end space-x-3 pt-3"><button id="${SCRIPT_PREFIX}confirm_cancel" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">取消</button><button id="${SCRIPT_PREFIX}confirm_ok" class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 dark:focus:ring-offset-gray-800">${confirmButtonText}</button></div></div>`; document.body.appendChild(dialogOverlay); document.body.classList.add(SCRIPT_PREFIX + 'modal-open'); setTimeout(() => { const dialogContent = $('div > div', dialogOverlay); if(dialogContent) { dialogContent.classList.remove('scale-95', 'opacity-0'); dialogContent.classList.add('scale-100', 'opacity-100'); } }, 10); const closeDialog = () => { const dialogContent = $('div > div', dialogOverlay); if(dialogContent) { dialogContent.classList.add('scale-95', 'opacity-0'); dialogContent.classList.remove('scale-100', 'opacity-100'); } setTimeout(() => { if (dialogOverlay) dialogOverlay.remove(); // Check if dialogOverlay still exists if (!$(`#${SCRIPT_PREFIX}edit_modal_overlay`)) { document.body.classList.remove(SCRIPT_PREFIX + 'modal-open'); } }, 300); }; $(`#${SCRIPT_PREFIX}confirm_cancel`, dialogOverlay).onclick = closeDialog; $(`#${SCRIPT_PREFIX}confirm_ok`, dialogOverlay).onclick = () => { onConfirm(); closeDialog(); }; // Diagnostic log for confirmation dialog overlay click dialogOverlay.onclick = (e) => { console.log('DSPM: Confirm Dialog Overlay clicked. Target:', e.target, 'Expected Overlay:', dialogOverlay); if (e.target === dialogOverlay) { console.log('DSPM: Closing Confirm Dialog due to overlay click.'); closeDialog(); } else { console.log('DSPM: Confirm Dialog Overlay click ignored, target was not overlay itself.'); } }; } function setupImportExportMenu(buttonElement) { if (!buttonElement) return; const newBtn = buttonElement.cloneNode(true); buttonElement.parentNode.replaceChild(newBtn, buttonElement); buttonElement = newBtn; buttonElement.addEventListener('click', (e) => { e.stopPropagation(); const menuId = SCRIPT_PREFIX + 'import_export_menu'; const existingMenu = $(`#${menuId}`); if (existingMenu) { existingMenu.remove(); return; } const menu = document.createElement('div'); menu.id = menuId; menu.className = `absolute z-[120] mt-1.5 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg py-1 dspm-fade-in`; menu.innerHTML = `<button data-action="export" class="w-full text-left px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors flex items-center"><i class="fas fa-file-export w-4 h-4 mr-2.5 text-gray-400 dark:text-gray-500"></i> 导出 JSON</button><button data-action="import" class="w-full text-left px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors flex items-center"><i class="fas fa-file-import w-4 h-4 mr-2.5 text-gray-400 dark:text-gray-500"></i> 导入 JSON</button>`; document.body.appendChild(menu); const btnRect = buttonElement.getBoundingClientRect(); menu.style.top = `${btnRect.bottom + 4}px`; menu.style.left = `${btnRect.right - menu.offsetWidth}px`; const closeMenu = () => menu.remove(); const clickOutsideHandler = (event) => { if (!menu.contains(event.target) && !buttonElement.contains(event.target)) { closeMenu(); document.removeEventListener('click', clickOutsideHandler, true); } }; document.addEventListener('click', clickOutsideHandler, true); $$('button', menu).forEach(btn => { btn.onclick = () => { console.log(`DSPM: Import/Export action: ${btn.dataset.action}`); if (btn.dataset.action === 'export') { const prompts = GM_getValue(SCRIPT_PREFIX + 'prompts', []); if (prompts.length === 0) { alert("没有可导出的提示词。"); closeMenu(); return; } const blob = new Blob([JSON.stringify(prompts, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `deepseek-prompts-${new Date().toISOString().slice(0,10)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log('DSPM: Export initiated.'); } else if (btn.dataset.action === 'import') { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (ev) => { const file = ev.target.files[0]; if (file) { console.log(`DSPM: File selected for import: ${file.name}`); const reader = new FileReader(); reader.onload = (le) => { try { const imported = JSON.parse(le.target.result); if (Array.isArray(imported) && imported.every(p => typeof p.title === 'string' && typeof p.content === 'string')) { GM_setValue(SCRIPT_PREFIX + 'prompts', imported); updateManagementPanelContent(); updateUsageDropdown(); alert('提示词导入成功!'); console.log('DSPM: Prompts imported successfully.'); } else { alert('导入失败:文件格式无效。'); console.error('DSPM: Invalid import file format.');} } catch (err) { console.error("DSPM: Import error:", err); alert('导入失败:无法解析文件。'); } }; reader.readAsText(file); } }; input.click(); console.log('DSPM: Import file dialog opened.'); } closeMenu(); }; }); }); } async function initScript() { try { console.log('DSPM: Initializing script...'); await injectCDN('https://cdn.tailwindcss.com/3.4.3', 'script'); console.log('DSPM: TailwindCSS CDN loaded.'); await injectCDN('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css', 'link'); console.log('DSPM: Font Awesome CDN loaded.'); initTheme(); createElements(); console.log('DSPM: Script initialized successfully.'); } catch (error) { console.error('DSPM: Failed to initialize script or load resources.', error); alert('提示词管理脚本加载资源失败,部分功能可能无法正常使用。请检查网络连接或浏览器控制台。'); } } const observer = new MutationObserver(debounce(() => { if (!$(`#${SCRIPT_PREFIX}usage_button`) && $('.ec4f5d61')) { console.log("DSPM: Detected button container via MutationObserver, attempting to create elements."); createElements(); } }, 500)); observer.observe(document.body, { childList: true, subtree: true }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initScript); } else { initScript(); } window.addEventListener('resize', debounce(() => { if (usagePromptDropdown && !usagePromptDropdown.classList.contains('hidden')) { updateUsageDropdownPosition(); } const importExportMenu = $(`#${SCRIPT_PREFIX}import_export_menu`); if (importExportMenu) { const importExportBtn = $(`#${SCRIPT_PREFIX}import_export_btn`, managementSidePanel); if (importExportBtn) { const btnRect = importExportBtn.getBoundingClientRect(); importExportMenu.style.top = `${btnRect.bottom + 4}px`; importExportMenu.style.left = `${btnRect.right - importExportMenu.offsetWidth}px`; } } }, 150)); })();