您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
PromptHelper:通用于 ChatGPT, Gemini, Claude, Kimi, DeepSeek, 通义、元宝、Google AI Studio、Grok 的侧边模板助手(仅保留默认“通用交互式提问模板”,默认选中;更稳事件触发)。
当前为
// ==UserScript== // @name PromptHelper // @namespace http://tampermonkey.net/ // @version 1.3 // @description PromptHelper:通用于 ChatGPT, Gemini, Claude, Kimi, DeepSeek, 通义、元宝、Google AI Studio、Grok 的侧边模板助手(仅保留默认“通用交互式提问模板”,默认选中;更稳事件触发)。 // @author Sauterne // @match http://chat.openai.com/* // @match https://chat.openai.com/* // @match http://chatgpt.com/* // @match https://chatgpt.com/* // @match http://gemini.google.com/* // @match https://gemini.google.com/* // @match http://claude.ai/* // @match https://claude.ai/* // @match http://demo.fuclaude.com/* // @match https://demo.fuclaude.com/* // @match http://www.kimi.com/* // @match https://www.kimi.com/* // @match http://kimi.com/* // @match https://kimi.com/* // @match http://kimi.moonshot.cn/* // @match https://kimi.moonshot.cn/* // @match http://chat.deepseek.com/* // @match https://chat.deepseek.com/* // @match http://www.tongyi.com/* // @match https://www.tongyi.com/* // @match http://yuanbao.tencent.com/chat/* // @match https://yuanbao.tencent.com/chat/* // @match http://aistudio.google.com/* // @match https://aistudio.google.com/* // @match http://grok.com/* // @match https://grok.com/* // @match http://www.grok.com/* // @match https://www.grok.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // === 可配置项(保持旧行为;仅提供开关不改变现有功能) === const SETTINGS = { forceOpenShadow: true }; // --- [核心修复] Closed Shadow DOM --- const originalAttachShadow = Element.prototype.attachShadow; Element.prototype.attachShadow = function(options) { if (SETTINGS.forceOpenShadow && options && options.mode === 'closed') { options.mode = 'open'; } return originalAttachShadow.call(this, options); }; // === 事件兼容工具 === function tryCreateInputEvent(type, opts = {}) { try { return new InputEvent(type, opts); } catch (_) { return new Event(type, { bubbles: !!opts.bubbles, cancelable: !!opts.cancelable }); } } function tryCreateKeyboardEvent(type, opts = {}) { try { return new KeyboardEvent(type, opts); } catch (_) { return new Event(type, { bubbles: !!opts.bubbles, cancelable: !!opts.cancelable }); } } // === ProseMirror 粘贴法(Claude) === function escapeHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } function textToHtmlPreserveBlankLines(text) { const lines = text.split('\n'); const paras = []; let buf = []; const flush = () => { if (!buf.length) return; const inner = buf.map(ln => escapeHtml(ln)).join('<br>'); paras.push(`<p>${inner}</p>`); buf = []; }; for (let i=0;i<lines.length;i++){ const ln=lines[i]; if (ln===''){flush(); paras.push('<p> </p>');} else buf.push(ln); } flush(); if (paras.length===0) paras.push('<p> </p>'); return paras.join(''); } function pasteIntoProseMirror(editableEl, plainText) { const html = textToHtmlPreserveBlankLines(plainText); editableEl.focus(); let ok=false; try{ const dt=new DataTransfer(); dt.setData('text/plain',plainText); dt.setData('text/html',html); const evt=new ClipboardEvent('paste',{bubbles:true,cancelable:true,clipboardData:dt}); ok=editableEl.dispatchEvent(evt);}catch(_){ok=false;} if(!ok){ try{document.execCommand('insertText',false,plainText);}catch(_){ editableEl.textContent=''; editableEl.dispatchEvent(new Event('input',{bubbles:true})); editableEl.textContent=plainText; editableEl.dispatchEvent(new Event('input',{bubbles:true})); } } } // === 原生 setter,确保受控组件感知 === const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set; const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set; function setNativeValue(el, value) { const setter = el.tagName === 'TEXTAREA' ? nativeTextareaValueSetter : el.tagName === 'INPUT' ? nativeInputValueSetter : null; if (setter) setter.call(el, value); else el.value = value; } window.addEventListener('DOMContentLoaded', () => { // --- 站点配置 --- const siteConfigs = { 'openai.com': { name: 'ChatGPT', inputSelector: '#prompt-textarea' }, 'chatgpt.com': { name: 'ChatGPT', inputSelector: '#prompt-textarea' }, 'gemini.google.com': { name: 'Gemini', shadowRootSelector: 'chat-app', inputSelector: 'div.initial-input-area textarea, rich-textarea .ql-editor, [contenteditable="true"][role="textbox"]' }, 'claude.ai': { name: 'Claude', inputSelector: '.ProseMirror[contenteditable="true"]' }, 'fuclaude.com': { name: 'Claude', inputSelector: '.ProseMirror[contenteditable="true"]' }, 'kimi.com': { name: 'Kimi', inputSelector: 'div.chat-input-editor[data-lexical-editor="true"], div[contenteditable="true"], textarea, [role="textbox"], [data-lexical-editor]' }, 'kimi.moonshot.cn': { name: 'Kimi', inputSelector: 'div.chat-input-editor[data-lexical-editor="true"], div[contenteditable="true"], textarea, [role="textbox"], [data-lexical-editor]' }, 'deepseek.com': { name: 'DeepSeek', inputSelector: 'textarea[placeholder*="随便聊点什么"], textarea[placeholder*="Ask me anything"], div[contenteditable="true"], #chat-input, [role="textbox"]' }, 'tongyi.com': { name: '通义', inputSelector: 'textarea[placeholder*="有问题,随时问通义"], textarea[placeholder*="问题"], textarea, div[contenteditable="true"], [role="textbox"]' }, 'yuanbao.tencent.com': { name: '腾讯元宝', inputSelector: 'textarea[placeholder*="输入问题"], textarea[placeholder*="问题"], textarea, div[contenteditable="true"], [role="textbox"]' }, 'aistudio.google.com': { name: 'Google AI Studio', shadowRootSelector: 'app-root', inputSelector: '[contenteditable="true"], textarea, [role="textbox"], [aria-label*="prompt"], [aria-label*="Prompt"], [placeholder*="prompt"], [placeholder*="Prompt"], .prompt-input, #prompt-input, input[type="text"]' }, 'grok.com': { name: 'Grok', inputSelector: 'form .query-bar textarea[aria-label], textarea[aria-label*="Grok"], textarea[aria-label*="向 Grok"], textarea' } }; // --- 仅保留一个默认模板,且不可删除 --- const DEFAULT_TEMPLATE_ID = 'default_interactive'; const defaultPrompts = { [DEFAULT_TEMPLATE_ID]: { name: "通用交互式提问模板", template: `SYSTEM ROLE — "Audit-Grade Researcher" You are a meticulous research analyst. You MUST perform genuine web research (“Search (Web Browsing)” or “Deep research” when available; via API use tool/function-calling to invoke web_search or equivalent), filter out uncertain/incorrect/irrelevant claims, and produce an audit-friendly, citation-backed reasoning chain. Do NOT reveal chain-of-thought or internal notes. Output language: Chinese only. INTERNAL DEEP THOUGHT (PRIVATE, NEVER PRINT): - T0 (before Gate 1) and T1 (before Gate 3): silently run a Deep Thought Monologue (first-principles → multi-perspective → recursive self-critique → synergistic synthesis). - 若仍有不确定或冲突,优先进入澄清而非猜测。 MODEL-SPECIFIC (TOOLS & BEHAVIOR): - If in Chat: use “Search” for recent/fact-sensitive claims; when complexity is high, escalate to “Deep research” for multi-step, cited synthesis. - If using the API: invoke web_search (tool/function) for retrieval; when available, enforce the 9-section output with Structured Outputs (JSON Schema). - If browsing/tools are unavailable, STOP and ask to enable them before proceeding. Do not produce conclusions without web access for source-required tasks. GATED WORKFLOW (Chinese output; do not proceed to conclusions unless Gate 1 passes): Gate 1 — Clarify First (pre-research): 识别问题是否含混/信息缺失/矛盾/错误前提。若存在问题,仅输出澄清块: • 问题诊断(≤120字) • 需要补充的关键信息(2–5条,多选/示例) • 可选默认假设(A/B/C…;声明“未确认不进入研究与结论”) Gate 2 — Mid-Research Check: 研究中若发现定义/口径/时段/法域冲突或证据矛盾,暂停并回到澄清模式。 Gate 3 — Pre-Final Check: 结论前核验:所有用作推理前提的断言均有 [S#];计算逐步复核;若仍有缺口,回澄清。 METHOD(仅在 Gate 1 通过后执行): A) 检索计划:给出你“实际执行”的检索式(引号、逻辑运算、site:/filetype:/date 限制)与动机。 B) 执行检索:打开并对比权威来源;剔除过时/仅观点/不可核验内容;必要时进一步检索补证。 C) 来源与证据表:ID | Title | URL | Publisher | Pub/Update Date | Key Evidence Used | Reliability(High/Med)。 D) 去伪存真记录:列明删除项与理由(过时、观点化、被反证、无法核验、无关)。 E) 已确认事实:仅留可交叉验证事实;关键结论力求 ≥3 个独立来源;每条附 [S#]。 F) 逻辑论证链:编号逐步推导,关键步骤附 [S#]。 G) 结论:中文作答,给出最优答案与置信度/不确定性范围。 H) 局限与更新触发条件:说明残余不确定性与可能改变结论的新证据。 NUMERICAL RIGOR: - 展示算式与单位换算的逐步过程;逐位检查关键数字;避免心算跳步。 STYLE: - 中文输出、措辞凝练;每个依赖联网的断言配 [S#] 内联引用;对明显可疑前提先发问再继续(如“为何 1+1 ≠ 2”需界定数学系统/语义上下文)。 FINAL OUTPUT FORMAT(九段固定): 1) 问题重述(若处于 Clarification Mode,仅输出“问题诊断/信息缺口/可选默认假设”) 2) 检索计划(含实际检索式与时间范围) 3) 来源与证据表(Sources Table) 4) 去伪存真记录(Exclusion Log) 5) 已确认事实清单(全部带 [S#]) 6) 逻辑论证链(逐步推导,步步有 [S#]) 7) 结论(最准确答案 + 置信度/范围) 8) 局限与更新触发条件 9) 参考文献(按 [S#] 列完整引文,含链接与访问日期) USER QUESTION (paste multi-paragraph content between the markers): <<<BEGIN_USER_QUESTION>>> {User Question} <<<END_USER_QUESTION>>> ` } }; // --- 国际化 --- const translations = { zh: { toggleButton: "Helper", panelTitle: "PromptHelper", collapseTitle: "收起", selectTemplate: "选择模板", newBtn: "新建", saveBtn: "保存", deleteBtn: "删除", templateName: "模板名称", templateNamePlaceholder: "为您的模板命名", templateContent: "模板内容 (使用 {User Question} 作为占位符)", yourQuestion: "您的问题", yourQuestionPlaceholder: "在此输入您的具体问题...", copyBtn: "复制到剪贴板", copiedBtn: "已复制!", submitBtn: "填入提问栏", selectDefault: "-- 选择一个模板 --", alertSaveSuccess: "模板已保存!", alertSaveError: "模板名称和内容不能为空!", alertDeleteConfirm: "确定要删除模板", alertDeleteError: "请先选择一个要删除的模板!", alertCopyError: "复制失败,请查看控制台。", alertSubmitError: "未找到当前网站的输入框。", alertTemplateError: "请先选择或创建一个模板!", alertCannotDeleteDefault: "默认模板不可删除。" }, en: { toggleButton: "Helper", panelTitle: "PromptHelper", collapseTitle: "Collapse", selectTemplate: "Select Template", newBtn: "New", saveBtn: "Save", deleteBtn: "Delete", templateName: "Template Name", templateNamePlaceholder: "Name your template", templateContent: "Template Content (use {User Question} as placeholder)", yourQuestion: "Your Question", yourQuestionPlaceholder: "Enter your specific question here...", copyBtn: "Copy to Clipboard", copiedBtn: "Copied!", submitBtn: "Fill into Input", selectDefault: "-- Select a template --", alertSaveSuccess: "Template saved!", alertSaveError: "Template name and content cannot be empty!", alertDeleteConfirm: "Are you sure you want to delete the template", alertDeleteError: "Please select a template to delete first!", alertCopyError: "Failed to copy. See console for details.", alertSubmitError: "Could not find the input box for the current site.", alertTemplateError: "Please select or create a template first!", alertCannotDeleteDefault: "The default template cannot be deleted." } }; let currentLang = GM_getValue('universal_prompt_helper_lang', 'zh'); function injectStyles() { GM_addStyle(` #prompt-helper-container { all: initial !important; } #prompt-helper-container *, #prompt-helper-container *::before, #prompt-helper-container *::after { all: unset !important; box-sizing: border-box !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; margin: 0 !important; padding: 0 !important; text-decoration: none !important; border: none !important; outline: none !important; } #prompt-helper-container { position: fixed !important; top: 100px !important; right: 0 !important; z-index: 99999 !important; font-size: 16px !important; color: #333 !important; line-height: 1.5 !important; } #prompt-helper-toggle { width: 40px !important; height: 100px !important; background-color: #007bff !important; color: white !important; border: none !important; border-radius: 10px 0 0 10px !important; cursor: pointer !important; writing-mode: vertical-rl !important; text-orientation: mixed !important; display: flex !important; align-items: center !important; justify-content: center !important; font-size: 16px !important; box-shadow: -2px 2px 5px rgba(0,0,0,0.2) !important; } #prompt-helper-content { position: absolute !important; top: 0 !important; right: 40px !important; width: 400px !important; background-color: #f8f9fa !important; border: 1px solid #dee2e6 !important; border-radius: 8px !important; box-shadow: -2px 2px 10px rgba(0,0,0,0.1) !important; padding: 15px !important; display: flex !important; flex-direction: column !important; gap: 15px !important; transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out !important; color: #333 !important; text-align: left !important; } #prompt-helper-content.hidden { transform: translateX(100%) !important; opacity: 0 !important; pointer-events: none !important; } #prompt-helper-content h3 { padding: 0 !important; font-size: 18px !important; color: #343a40 !important; text-align: center !important; font-weight: bold !important; } #prompt-helper-content .ph-section { display: flex !important; flex-direction: column !important; gap: 8px !important; } #prompt-helper-content label { font-weight: bold !important; color: #495057 !important; font-size: 14px !important; } #prompt-helper-content select, #prompt-helper-content input, #prompt-helper-content textarea { width: 100% !important; padding: 8px !important; border: 1px solid #ced4da !important; border-radius: 4px !important; font-size: 14px !important; color: #333 !important; background-color: #fff !important; line-height: 1.5 !important; } #prompt-helper-content textarea { resize: vertical !important; min-height: 100px !important; } #prompt-helper-content #ph-template-body { height: 150px !important; } #prompt-helper-content #ph-user-question { height: 80px !important; } #prompt-helper-content .ph-button-group { display: flex !important; gap: 10px !important; justify-content: space-between !important; } #prompt-helper-content .ph-button-group button { flex-grow: 1 !important; } #prompt-helper-content button { padding: 10px !important; border-radius: 5px !important; border: none !important; cursor: pointer !important; font-size: 14px !important; font-weight: bold !important; transition: background-color 0.2s, color 0.2s !important; color: white !important; } #prompt-helper-content button:disabled { cursor: not-allowed !important; opacity: 0.7 !important; } #prompt-helper-container .ph-btn-primary { background-color: #007bff !important; } #prompt-helper-container .ph-btn-primary:hover { background-color: #0056b3 !important; } #prompt-helper-container .ph-btn-secondary { background-color: #6c757d !important; } #prompt-helper-container .ph-btn-secondary:hover { background-color: #5a6268 !important; } #prompt-helper-container .ph-btn-success { background-color: #28a745 !important; } #prompt-helper-container .ph-btn-success:hover { background-color: #218838 !important; } #prompt-helper-container .ph-btn-danger { background-color: #dc3545 !important; } #prompt-helper-container .ph-btn-danger:hover { background-color: #c82333 !important; } #prompt-helper-container button:focus-visible, #prompt-helper-container select:focus-visible, #prompt-helper-container input:focus-visible, #prompt-helper-container textarea:focus-visible { outline: 2px solid #0056b3 !important; outline-offset: 2px !important; } #prompt-helper-container .ph-header { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 10px !important; padding: 0 !important; } #prompt-helper-container #ph-collapse-btn { font-size: 24px !important; cursor: pointer !important; color: #6c757d !important; border: none !important; background: none !important; padding: 0 5px !important; line-height: 1 !important; } #prompt-helper-container #ph-lang-toggle { font-size: 12px !important; color: #007bff !important; background: none !important; border: 1px solid #007bff !important; padding: 2px 6px !important; border-radius: 4px !important; cursor: pointer !important; } `); } function buildUI() { const create = (tag, id, classes = [], attributes = {}, children = []) => { const el = document.createElement(tag); if (id) el.id = id; if (classes.length) el.classList.add(...classes); for (const key in attributes) el.setAttribute(key, attributes[key]); for (const child of children) el.appendChild(child); return el; }; const D = {}; const container = create('div', 'prompt-helper-container'); D.toggleButton = create('button', 'prompt-helper-toggle'); D.contentPanel = create('div', 'prompt-helper-content', ['hidden']); D.langToggleButton = create('button', 'ph-lang-toggle', [], {}, [document.createTextNode('中/En')]); D.title = create('h3', 'ph-title'); D.collapseButton = create('button', 'ph-collapse-btn'); const header = create('div', 'ph-header', ['ph-header'], {}, [D.langToggleButton, D.title, D.collapseButton]); D.labelSelect = create('label', 'ph-label-select', [], { for: 'ph-template-select' }); D.templateSelect = create('select', 'ph-template-select'); D.newBtn = create('button', 'ph-new-btn', ['ph-btn-primary']); D.saveBtn = create('button', 'ph-save-btn', ['ph-btn-success']); D.deleteBtn = create('button', 'ph-delete-btn', ['ph-btn-danger']); const section1 = create('div', null, ['ph-section'], {}, [ D.labelSelect, D.templateSelect, create('div', null, ['ph-button-group'], {}, [D.newBtn, D.saveBtn, D.deleteBtn]) ]); D.labelName = create('label', 'ph-label-name', [], { for: 'ph-template-name' }); D.templateNameInput = create('input', 'ph-template-name', [], { type: 'text' }); D.labelContent = create('label', 'ph-label-content', [], { for: 'ph-template-body' }); D.templateBodyTextarea = create('textarea', 'ph-template-body'); const section2 = create('div', null, ['ph-section'], {}, [D.labelName, D.templateNameInput, D.labelContent, D.templateBodyTextarea]); D.labelQuestion = create('label', 'ph-label-question', [], { for: 'ph-user-question' }); D.userQuestionTextarea = create('textarea', 'ph-user-question'); const section3 = create('div', null, ['ph-section'], {}, [D.labelQuestion, D.userQuestionTextarea]); D.copyBtn = create('button', 'ph-copy-btn', ['ph-btn-secondary']); D.submitBtn = create('button', 'ph-submit-btn', ['ph-btn-primary']); const section4 = create('div', null, ['ph-section'], {}, [ create('div', null, ['ph-button-group'], {}, [D.copyBtn, D.submitBtn]) ]); D.contentPanel.append(header, section1, section2, section3, section4); container.append(D.toggleButton, D.contentPanel); return { container, elements: D }; } function findInputElement() { const siteConfig = getCurrentSiteConfig(); if (!siteConfig) { console.log('[PromptHelper] 未找到当前网站配置'); return null; } console.log(`[PromptHelper] 正在查找 ${window.location.hostname} 的输入元素...`); console.log(`[PromptHelper] 使用选择器: ${siteConfig.inputSelector}`); let inputElement = null; // 1) Shadow DOM if (siteConfig.shadowRootSelector) { const host = document.querySelector(siteConfig.shadowRootSelector); if (host && host.shadowRoot) { const elementInShadow = host.shadowRoot.querySelector(siteConfig.inputSelector); if (elementInShadow) inputElement = elementInShadow; } } // 2) 常规 DOM if (!inputElement) { const selectors = siteConfig.inputSelector.split(',').map(s => s.trim()); for (const selector of selectors) { const elements = document.querySelectorAll(selector); for (const element of elements) { if (!element.closest('#prompt-helper-container')) { inputElement = element; break; } } if (inputElement) break; } } // 3) AI Studio 回退 if (!inputElement && window.location.hostname.includes('aistudio.google.com')) { const aiStudioSelectors = [ '[contenteditable="true"]','textarea','[role="textbox"]','[aria-label*="prompt"]','[aria-label*="Prompt"]','[aria-label*="message"]','[aria-label*="Message"]','[placeholder*="prompt"]','[placeholder*="Prompt"]','[placeholder*="message"]','[placeholder*="Message"]','[data-testid*="prompt"]','[data-testid*="input"]','.prompt-input','.chat-input','.message-input','input[type="text"]','div[spellcheck="true"]','[data-lexical-editor]','.editor-input' ]; for (const selector of aiStudioSelectors) { const elements = document.querySelectorAll(selector); for (const element of elements) { if (!element.closest('#prompt-helper-container')) { const style = window.getComputedStyle(element); const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; const isEditable = !element.disabled && !element.readOnly && (element.contentEditable === 'true' || element.tagName.toLowerCase() === 'textarea' || element.tagName.toLowerCase() === 'input'); if (isVisible && isEditable) { inputElement = element; break; } } } if (inputElement) break; } } // 4) DeepSeek 回退 if (!inputElement && window.location.hostname.includes('deepseek.com')) { const fallbackSelectors = [ 'textarea','[contenteditable="true"]','[role="textbox"]','input[type="text"]','.chat-input','[data-placeholder]','[aria-label*="输入"]','[aria-label*="input"]','[placeholder*="聊"]','[placeholder*="chat"]' ]; for (const selector of fallbackSelectors) { const elements = document.querySelectorAll(selector); for (const element of elements) { if (!element.closest('#prompt-helper-container')) { const style = window.getComputedStyle(element); if (style.display !== 'none' && style.visibility !== 'hidden' && !element.disabled && !element.readOnly) { inputElement = element; break; } } } if (inputElement) break; } } // 5) Grok 回退 if (!inputElement && window.location.hostname.includes('grok.com')) { const grokSelectors = [ 'form .query-bar textarea[aria-label]', 'textarea[aria-label*="Grok"]', 'textarea[aria-label*="向 Grok"]', 'textarea' ]; for (const selector of grokSelectors) { const elements = document.querySelectorAll(selector); for (const el of elements) { if (!el.closest('#prompt-helper-container')) { const st = window.getComputedStyle(el); const visible = st.display !== 'none' && st.visibility !== 'hidden' && st.opacity !== '0'; const editable = !el.disabled && !el.readOnly; if (visible && editable) { inputElement = el; break; } } } if (inputElement) break; } } if (inputElement) { console.log('[PromptHelper] 成功找到输入元素:', { tagName: inputElement.tagName, className: inputElement.className, id: inputElement.id, placeholder: inputElement.placeholder, contentEditable: inputElement.contentEditable, element: inputElement }); } else { console.log('[PromptHelper] 未找到输入元素'); const allInputs = document.querySelectorAll('textarea, input, [contenteditable="true"], [role="textbox"]'); allInputs.forEach((el, index) => { console.log(`[PromptHelper] 候选元素 ${index + 1}:`, { tagName: el.tagName, className: el.className, id: el.id, placeholder: el.placeholder, contentEditable: el.contentEditable, isVisible: window.getComputedStyle(el).display !== 'none', element: el }); }); } return inputElement; } function init() { if (document.getElementById('prompt-helper-container')) return; if (window.promptHelperInitialized) return; window.promptHelperInitialized = true; injectStyles(); const { container, elements: D } = buildUI(); const addToDOM = () => { if (document.body) { document.body.appendChild(container); } else { setTimeout(addToDOM, 100); } }; addToDOM(); let prompts = {}; const updateUI = () => { const t = translations[currentLang]; D.toggleButton.textContent = t.toggleButton; D.title.textContent = t.panelTitle; D.collapseButton.title = t.collapseTitle; D.collapseButton.textContent = '\u00d7'; D.labelSelect.textContent = t.selectTemplate; D.newBtn.textContent = t.newBtn; D.saveBtn.textContent = t.saveBtn; D.deleteBtn.textContent = t.deleteBtn; D.labelName.textContent = t.templateName; D.templateNameInput.placeholder = t.templateNamePlaceholder; D.labelContent.textContent = t.templateContent; D.labelQuestion.textContent = t.yourQuestion; D.userQuestionTextarea.placeholder = t.yourQuestionPlaceholder; D.copyBtn.textContent = t.copyBtn; D.submitBtn.textContent = t.submitBtn; populateDropdown(); // 默认选择“通用交互式提问模板” if (prompts[DEFAULT_TEMPLATE_ID]) D.templateSelect.value = DEFAULT_TEMPLATE_ID; displaySelectedPrompt(); }; const savePrompts = () => GM_setValue('universal_prompt_helper_prompts', JSON.stringify(prompts)); // 迁移:移除旧版本的三个默认模板(不影响用户自建模板) function removeLegacyDefaults(obj) { const legacyIds = ['prompt_1', 'prompt_2', 'prompt_3']; const legacyNames = new Set(['通用回答模板','代码评审模板','英文润色模板']); legacyIds.forEach(id => { if (id in obj) delete obj[id]; }); for (const k of Object.keys(obj)) { if (legacyNames.has(obj[k]?.name)) delete obj[k]; } } const populateDropdown = () => { const currentSelection = D.templateSelect.value; D.templateSelect.textContent = ''; const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = translations[currentLang].selectDefault; D.templateSelect.appendChild(defaultOption); // 默认模板优先 if (prompts[DEFAULT_TEMPLATE_ID]) { const opt = document.createElement('option'); opt.value = DEFAULT_TEMPLATE_ID; opt.textContent = prompts[DEFAULT_TEMPLATE_ID].name; D.templateSelect.appendChild(opt); } for (const id in prompts) { if (id === DEFAULT_TEMPLATE_ID) continue; const option = document.createElement('option'); option.value = id; option.textContent = prompts[id].name; D.templateSelect.appendChild(option); } if (prompts[currentSelection]) D.templateSelect.value = currentSelection; }; const displaySelectedPrompt = () => { const selectedId = D.templateSelect.value; if (selectedId && prompts[selectedId]) { D.templateNameInput.value = prompts[selectedId].name; D.templateBodyTextarea.value = prompts[selectedId].template; } else { D.templateNameInput.value = ''; D.templateBodyTextarea.value = ''; } D.deleteBtn.disabled = (selectedId === DEFAULT_TEMPLATE_ID); // 默认模板不可删除 }; const generateFinalPrompt = () => { const template = D.templateBodyTextarea.value; const question = D.userQuestionTextarea.value; if (!template) { alert(translations[currentLang].alertTemplateError); return null; } return template.replace('{User Question}', question); }; const loadPrompts = () => { const saved = GM_getValue('universal_prompt_helper_prompts', null); if (saved) { try { prompts = JSON.parse(saved) || {}; } catch { prompts = {}; } removeLegacyDefaults(prompts); // 删除旧默认模板 if (!prompts[DEFAULT_TEMPLATE_ID]) { prompts[DEFAULT_TEMPLATE_ID] = defaultPrompts[DEFAULT_TEMPLATE_ID]; } savePrompts(); } else { prompts = { ...defaultPrompts }; savePrompts(); } updateUI(); // 更新并默认选中 // 再确保选中默认模板一次(防第三方样式/脚本影响) if (prompts[DEFAULT_TEMPLATE_ID]) { D.templateSelect.value = DEFAULT_TEMPLATE_ID; displaySelectedPrompt(); } }; // 事件绑定 D.toggleButton.addEventListener('click', () => D.contentPanel.classList.remove('hidden')); D.collapseButton.addEventListener('click', () => D.contentPanel.classList.add('hidden')); D.langToggleButton.addEventListener('click', () => { currentLang = currentLang === 'zh' ? 'en' : 'zh'; GM_setValue('universal_prompt_helper_lang', currentLang); updateUI(); }); D.templateSelect.addEventListener('change', displaySelectedPrompt); D.newBtn.addEventListener('click', () => { D.templateSelect.value = ''; D.templateNameInput.value = ''; D.templateBodyTextarea.value = ''; D.templateNameInput.focus(); D.deleteBtn.disabled = true; }); D.saveBtn.addEventListener('click', () => { const name = D.templateNameInput.value.trim(); const template = D.templateBodyTextarea.value.trim(); if (!name || !template) { alert(translations[currentLang].alertSaveError); return; } let selectedId = D.templateSelect.value || `prompt_${Date.now()}`; prompts[selectedId] = { name, template }; savePrompts(); populateDropdown(); D.templateSelect.value = selectedId; displaySelectedPrompt(); alert(`${translations[currentLang].alertSaveSuccess} "${name}"`); }); D.deleteBtn.addEventListener('click', () => { const selectedId = D.templateSelect.value; if (!selectedId) { alert(translations[currentLang].alertDeleteError); return; } if (selectedId === DEFAULT_TEMPLATE_ID) { alert(translations[currentLang].alertCannotDeleteDefault); return; } if (confirm(`${translations[currentLang].alertDeleteConfirm} "${prompts[selectedId].name}"?`)) { delete prompts[selectedId]; savePrompts(); populateDropdown(); // 删除后默认选中默认模板 if (prompts[DEFAULT_TEMPLATE_ID]) D.templateSelect.value = DEFAULT_TEMPLATE_ID; displaySelectedPrompt(); } }); D.copyBtn.addEventListener('click', () => { const finalPrompt = generateFinalPrompt(); if (finalPrompt) { navigator.clipboard.writeText(finalPrompt).then(() => { const originalText = D.copyBtn.textContent; D.copyBtn.textContent = translations[currentLang].copiedBtn; D.copyBtn.disabled = true; setTimeout(() => { D.copyBtn.textContent = originalText; D.copyBtn.disabled = false; }, 2000); }).catch(err => { console.error('Copy failed:', err); alert(translations[currentLang].alertCopyError); }); } }); D.submitBtn.addEventListener('click', () => { const finalPrompt = generateFinalPrompt(); if (!finalPrompt) return; const inputElement = findInputElement(); if (!inputElement) { alert(translations[currentLang].alertSubmitError); return; } // textarea if (inputElement.tagName.toLowerCase() === 'textarea') { if (window.location.hostname.includes('tongyi.com')) { // 通义:老逻辑 + 受控兜底 const reactKey = Object.keys(inputElement).find(key => key.startsWith('__reactInternalInstance') || key.startsWith('__reactFiber') || key.startsWith('__reactProps')); if (reactKey) { try { const fiberNode = inputElement[reactKey]; const possiblePaths = [ fiberNode?.memoizedProps?.onChange, fiberNode?.return?.memoizedProps?.onChange, fiberNode?.return?.return?.memoizedProps?.onChange, fiberNode?.pendingProps?.onChange ]; for (const onChange of possiblePaths) { if (onChange && typeof onChange === 'function') { const fakeEvent = { target: { value: finalPrompt }, currentTarget: { value: finalPrompt }, preventDefault: () => {}, stopPropagation: () => {} }; onChange(fakeEvent); break; } } } catch (e) { console.log('[PromptHelper] React状态操作失败:', e); } } inputElement.focus(); inputElement.value = ''; inputElement.value = finalPrompt; try { Object.defineProperty(inputElement, 'value', { value: finalPrompt, writable: true, configurable: true }); } catch (_){} [ new Event('focus', { bubbles: true }), tryCreateInputEvent('beforeinput', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' }), tryCreateInputEvent('input', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' }), new Event('change', { bubbles: true }), tryCreateKeyboardEvent('keydown', { bubbles: true, key: 'a' }), tryCreateKeyboardEvent('keyup', { bubbles: true, key: 'a' }), new Event('blur', { bubbles: true }) ].forEach((ev, i) => setTimeout(() => inputElement.dispatchEvent(ev), i * 10)); setTimeout(() => { if (inputElement.value !== finalPrompt) inputElement.value = finalPrompt; inputElement.blur(); setTimeout(() => { inputElement.focus(); inputElement.value = finalPrompt; inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); inputElement.dispatchEvent(new Event('propertychange', { bubbles: true })); window.dispatchEvent(new Event('resize')); }, 50); }, 150); } else if (window.location.hostname.includes('grok.com')) { // Grok:原生 setter + 事件序列 inputElement.focus(); setNativeValue(inputElement, ''); inputElement.dispatchEvent(new Event('input', { bubbles: true })); setNativeValue(inputElement, finalPrompt); try { inputElement.setAttribute('value', finalPrompt); } catch (_){} inputElement.dispatchEvent(tryCreateInputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertFromPaste', data: finalPrompt })); inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: finalPrompt })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); ['keydown','keypress','keyup'].forEach(type => inputElement.dispatchEvent(tryCreateKeyboardEvent(type, { bubbles: true, cancelable: true, key: 'a', code: 'KeyA' }))); try { inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch (_){} setTimeout(() => { inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); }, 50); } else { // ChatGPT 等原逻辑 if (window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com')) { inputElement.value = finalPrompt; const isChrome = navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Firefox'); if (isChrome) { setTimeout(() => { inputElement.focus(); inputElement.value = finalPrompt; if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertText' })); let protectionCount = 0; const protect = () => { if (protectionCount < 20) { const cur = inputElement.value; if (cur.replace(/\n/g,'') === finalPrompt.replace(/\n/g,'') && !cur.includes('\n') && finalPrompt.includes('\n')) { inputElement.value = finalPrompt; if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertText' })); } protectionCount++; setTimeout(protect, 100); } }; setTimeout(protect, 100); }, 50); } else { inputElement.dispatchEvent(new Event('input', { bubbles: true })); if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } } else { inputElement.value = finalPrompt; } // DeepSeek 伴随 div 镜像 if (window.location.hostname.includes('deepseek.com')) { const parentDiv = inputElement.parentElement; if (parentDiv) { let displayDiv = parentDiv.querySelector('.b13855df'); if (!displayDiv) { const allDivs = parentDiv.querySelectorAll('div'); for (const div of allDivs) { if (!div.classList.contains('_24fad49') && div !== parentDiv) { displayDiv = div; break; } } } if (displayDiv) { displayDiv.innerHTML = ''; finalPrompt.split('\n').forEach((line, idx) => { if (idx>0) displayDiv.appendChild(document.createElement('br')); displayDiv.appendChild(document.createTextNode(line)); }); } } } } // contenteditable } else if (inputElement.getAttribute('contenteditable') === 'true') { if (window.location.hostname.includes('claude.ai') || window.location.hostname.includes('fuclaude.com')) { pasteIntoProseMirror(inputElement, finalPrompt); inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); try { const range=document.createRange(); const sel=window.getSelection(); range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); } catch(_){} } else { if (window.location.hostname.includes('claude.ai') || window.location.hostname.includes('fuclaude.com') || window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com')) { inputElement.innerHTML = ''; const lines = finalPrompt.split('\n'); lines.forEach((line, index) => { if (index > 0) inputElement.appendChild(document.createElement('br')); if (line.length > 0) inputElement.appendChild(document.createTextNode(line)); else if (index < lines.length - 1) inputElement.appendChild(document.createElement('br')); }); const isChrome = navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Firefox'); if (isChrome) { const needEscape = (window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com')); const htmlWithBreaks = needEscape ? escapeHtml(finalPrompt).replace(/\n/g,'<br>') : finalPrompt.replace(/\n/g,'<br>'); setTimeout(() => { inputElement.focus(); inputElement.innerHTML = htmlWithBreaks; const range=document.createRange(); const sel=window.getSelection(); range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertFromPaste' })); let protectionCount = 0; const protect = () => { if (protectionCount < 20) { const currentHtml = inputElement.innerHTML; const currentText = inputElement.textContent || inputElement.innerText; if (currentText.replace(/\n/g,'') === finalPrompt.replace(/\n/g,'') && !currentHtml.includes('<br>') && finalPrompt.includes('\n')) { inputElement.innerHTML = htmlWithBreaks; try { const r=document.createRange(); const s=window.getSelection(); r.selectNodeContents(inputElement); r.collapse(false); s.removeAllRanges(); s.addRange(r);} catch(_){} } protectionCount++; setTimeout(protect, 100); } }; setTimeout(protect, 100); }, 50); } } else { inputElement.textContent = finalPrompt; } } // 兜底 } else { if ('value' in inputElement) inputElement.value = finalPrompt; if (inputElement.textContent !== undefined) inputElement.textContent = finalPrompt; if (inputElement.innerText !== undefined) inputElement.innerText = finalPrompt; } // 通用事件 ['input','change','keydown','keyup','paste'].forEach(type => { let ev; if (type==='input') ev = tryCreateInputEvent('input', { bubbles:true, cancelable:true, inputType:'insertText', data: finalPrompt }); else if (type==='keydown' || type==='keyup') ev = tryCreateKeyboardEvent(type, { bubbles:true, cancelable:true, key:'a', code:'KeyA' }); else ev = new Event(type, { bubbles:true, cancelable:true }); inputElement.dispatchEvent(ev); }); if (window.location.hostname.includes('deepseek.com')) { ['keydown','keypress','keyup'].forEach(t => inputElement.dispatchEvent(tryCreateKeyboardEvent(t, { bubbles:true, cancelable:true, key:'a', code:'KeyA', which:65, keyCode:65 }))); inputElement.dispatchEvent(new Event('compositionstart', { bubbles: true })); inputElement.dispatchEvent(new Event('compositionupdate', { bubbles: true })); inputElement.dispatchEvent(new Event('compositionend', { bubbles: true })); } // 聚焦与光标 inputElement.focus(); if (inputElement.tagName.toLowerCase() === 'textarea' || inputElement.type === 'text') { try { inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch (_){} } else if (inputElement.getAttribute('contenteditable') === 'true') { const range=document.createRange(); const sel=window.getSelection(); if (sel && inputElement.childNodes.length > 0) { range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); } } // 轻微延迟再触发 setTimeout(() => { inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); const specialSites = ['deepseek.com','kimi.moonshot.cn','kimi.com','www.kimi.com','tongyi.com','yuanbao.tencent.com']; const currentSiteCheck = specialSites.find(site => window.location.hostname.includes(site)); if (currentSiteCheck) console.log(`[PromptHelper] 延迟检查 ${currentSiteCheck} 状态`); }, 100); // 额外同步检查 const specialSites = ['deepseek.com','kimi.moonshot.cn','kimi.com','www.kimi.com','tongyi.com','yuanbao.tencent.com','aistudio.google.com']; const currentSite = specialSites.find(site => window.location.hostname.includes(site)); if (currentSite) { const skipComplexProcessing = (currentSite === 'kimi.moonshot.cn' || currentSite === 'kimi.com' || currentSite === 'www.kimi.com') && inputElement.getAttribute('contenteditable') === 'true'; if (!skipComplexProcessing) { setTimeout(() => { const parentDiv = inputElement.parentElement; if (parentDiv) { inputElement.blur(); setTimeout(() => { inputElement.focus(); try { if (inputElement.tagName.toLowerCase() === 'textarea' || inputElement.type === 'text') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch(_){} }, 50); window.dispatchEvent(new Event('resize')); const reactKey = Object.keys(inputElement).find(key => key.startsWith('__reactInternalInstance') || key.startsWith('__reactFiber')); if (reactKey) { try { const fiberNode = inputElement[reactKey]; if (fiberNode && fiberNode.memoizedProps && typeof fiberNode.memoizedProps.onChange === 'function') { const fakeEvent = { target: { value: finalPrompt }, currentTarget: { value: finalPrompt }, preventDefault: () => {}, stopPropagation: () => {} }; fiberNode.memoizedProps.onChange(fakeEvent); } } catch (e) { console.log(`[PromptHelper] ${currentSite} React状态更新失败:`, e); } } if (currentSite === 'tongyi.com') { inputElement.focus(); inputElement.value = ''; const txt = finalPrompt; for (let i=0;i<txt.length;i++){ const ch = txt[i]; inputElement.value += ch; [ tryCreateKeyboardEvent('keydown', { bubbles:true, cancelable:true, key: ch }), tryCreateInputEvent('input', { bubbles:true, cancelable:true, data: ch, inputType: 'insertText' }), tryCreateKeyboardEvent('keyup', { bubbles:true, cancelable:true, key: ch }) ].forEach(ev => inputElement.dispatchEvent(ev)); } inputElement.dispatchEvent(new Event('change', { bubbles: true })); inputElement.dispatchEvent(new Event('blur', { bubbles: true })); setTimeout(() => inputElement.focus(), 50); } else if (currentSite === 'yuanbao.tencent.com') { try { inputElement.setAttribute('value', finalPrompt); } catch(_){} } else if (currentSite === 'aistudio.google.com') { const angularKey = Object.keys(inputElement).find(key => key.startsWith('__ngContext') || key.startsWith('__ng') || key.includes('angular')); if (angularKey) { try { const ngZone = window.ng?.getComponent?.(inputElement); if (ngZone) { ngZone.run(() => { inputElement.value = finalPrompt; if (inputElement.textContent !== undefined) inputElement.textContent = finalPrompt; }); } } catch (e) { console.log('[PromptHelper] Angular状态操作失败:', e); } } if (inputElement.getAttribute('contenteditable') === 'true') { inputElement.innerHTML = ''; finalPrompt.split('\n').forEach((line, idx) => { if (idx>0) inputElement.appendChild(document.createElement('br')); inputElement.appendChild(document.createTextNode(line)); }); } [ new Event('focus', { bubbles: true }), tryCreateInputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText' }), new Event('change', { bubbles: true }), new Event('blur', { bubbles: true }) ].forEach((ev, i) => setTimeout(() => inputElement.dispatchEvent(ev), i * 20)); } } setTimeout(() => { if ('value' in inputElement && inputElement.value !== finalPrompt) { inputElement.value = finalPrompt; inputElement.dispatchEvent(new Event('input', { bubbles: true })); } }, 300); }, 500); } } D.userQuestionTextarea.value = ''; D.contentPanel.classList.add('hidden'); }); loadPrompts(); } function getCurrentSiteConfig() { const hostname = window.location.hostname; for (const key in siteConfigs) { if (hostname.includes(key)) return siteConfigs[key]; } return null; } if (getCurrentSiteConfig()) { init(); } }); })();