我想通过快速启动工具向 AI 提问,这样想到什么立刻就问,可能让我思路顺畅一点。
最简单的方法就是通过网址传入自己的问题,但是很多 AI 的网页版不支持这个用法。
那就让他支持!就用 AI 写了个脚本搞这个事情,网址支持两个参数:
q要问的问题m选择的模型
| 站点 | 地址 | 模型参数(`m=`) |
|---|---|---|
| DeepSeek | chat.deepseek.com | `default` 快速 / `expert` 专家(R1) / `vision` 识图 |
| Kimi | www.kimi.com | `fast` 快速 / `think` 思考 / `agent` Agent / `cluster` Agent 集群 |
但是不自动提交,方便修改一下什么的(个人喜好)
脚本……脚本也不重要吧,大家都有 AI,自己按需求定制就行了,反正就是个思路而已
// ==UserScript==
// @name AI 网页版 Query 参数问答填充
// @namespace https://github.com/ai-web-query-userscript
// @version 0.2.0
// @description 通过 URL query 参数(q=问题, m=模型)向 AI 网页版输入框自动填入问答内容,仅填充不提交。支持 DeepSeek、Kimi。
// @author 大橘
// @match https://chat.deepseek.com/*
// @match https://www.kimi.com/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ============================================================
// 区域:适配器注册表 Adapters
// 每个站点一个适配器配置,新增站点只需在此追加一项
// ============================================================
// #region Adapters
/**
* DeepSeek 适配器(已通过 CDP 实测验证)
* - 输入框:textarea,placeholder="给 DeepSeek 发送消息 "
* - 模型选择:data-model-type 属性,role="radio",激活态 aria-checked="true"
*/
const deepseekAdapter = {
id: 'deepseek', // 站点唯一标识
match: 'https://chat.deepseek.com/*', // @match 用通配
inputSelector: [ // 输入框选择器,按优先级
'textarea[placeholder="给 DeepSeek 发送消息"]', // 最稳:中文 placeholder 不易变
'._24fad49 textarea', // 兜底:父级 class
'textarea' // 最后兜底:页面唯一 textarea
],
inputType: 'textarea', // 输入框类型
// 模型选择器映射:key=参数值, value={ selector }
// 选择器直接指向可点击的 radio 元素
modelConfig: {
default: { selector: '[data-model-type="default"]' }, // 快速模式
expert: { selector: '[data-model-type="expert"]' }, // 专家模式(R1 深度思考)
vision: { selector: '[data-model-type="vision"]' } // 识图模式
},
// 激活态判定:取元素自身属性
modelIsActive: (el) => el.getAttribute('aria-checked') === 'true'
};
/**
* Kimi 适配器(已通过 CDP 实测验证)
* - 输入框:contenteditable div,class="chat-input-editor",Lexical 富文本编辑器
* - 模型选择:点击 .current-model 展开下拉,选项为 .model-item,激活态带 .checked 类
* - fast = K2.6 快速
* - think = K2.6 思考(默认)
* - agent = K2.6 Agent
* - cluster = K2.6 Agent 集群
*/
const kimiAdapter = {
id: 'kimi', // 站点唯一标识
match: 'https://www.kimi.com/*', // @match 用通配
inputSelector: [ // 输入框选择器,按优先级
'.chat-input-editor', // 主选择器:Lexical 编辑器
'[data-lexical-editor="true"]', // 兜底:data 属性
'[contenteditable="true"].editor' // 最后兜底
],
inputType: 'contenteditable', // 输入框类型:富文本
// 模型配置:下拉式切换
// triggerSelector: 展开下拉的按钮
// optionSelector: 选项选择器(按文本匹配或 class 匹配)
modelConfig: {
fast: { optionText: 'K2.6 快速' }, // 快速响应
think: { optionText: 'K2.6 思考' }, // 多轮搜索思考
agent: { optionText: 'K2.6 Agent' }, // 调研、PPT、网站、文档、表格
cluster: { optionText: 'K2.6 Agent 集群' } // 海量搜索、长文写作、批量处理
},
// 下拉触发器选择器
modelTrigger: '.current-model',
// 激活态判定:从当前显示文本判断,或从下拉项 .checked 判断
modelIsActive: (el, key, config) => {
// el 是下拉触发器 .current-model,从文本判断是否已选目标
const text = el.textContent?.trim() || '';
return text.includes(config.optionText.replace(/\s*$/, ''));
},
// 在展开的下拉中找到对应选项并点击
modelFindOption: (config) => {
const items = document.querySelectorAll('.model-item');
for (const item of items) {
if (item.textContent?.includes(config.optionText)) {
return item;
}
}
return null;
}
};
/** 适配器列表:新增站点在此追加即可 */
const adapters = [deepseekAdapter, kimiAdapter];
// #endregion Adapters
// ============================================================
// 区域:参数解析 ParamParser
// 从 URL query 读取 q(问题)与 m(模型)
// ============================================================
// #region ParamParser
/**
* 从当前 URL 读取 query 参数
* @param {string} name - 参数名
* @returns {string|null} 解码后的值,不存在则 null
*/
function getQueryParam(name) {
const params = new URLSearchParams(window.location.search); // 解析 search 串
const value = params.get(name); // 读取指定参数
return value ? decodeURIComponent(value) : null; // 解码并返回(空值返回 null)
}
// #endregion ParamParser
// ============================================================
// 区域:DOM 工具 DOMUtils
// 等待元素、填充输入框、点击等基础操作
// ============================================================
// #region DOMUtils
/** 默认等待超时(毫秒) */
const WAIT_TIMEOUT = 10000;
/**
* 等待匹配选择器的元素出现(MutationObserver + 超时兜底)
* @param {string[]} selectors - 选择器数组,按优先级尝试
* @param {number} timeout - 超时毫秒
* @returns {Promise<Element|null>} 命中的元素,超时返回 null
*/
function waitForElement(selectors, timeout = WAIT_TIMEOUT) {
return new Promise((resolve) => {
const found = findFirst(selectors); // 立即查找一次
if (found) { resolve(found); return; }
const observer = new MutationObserver(() => { // 监听 DOM 变化
const el = findFirst(selectors);
if (el) { observer.disconnect(); resolve(el); }
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); // 超时兜底
});
}
/**
* 按选择器数组优先级查找第一个命中的元素
* @param {string[]} selectors
* @returns {Element|null}
*/
function findFirst(selectors) {
for (const sel of selectors) { // 按优先级遍历
const el = document.querySelector(sel); // 尝试匹配
if (el) return el; // 命中即返回
}
return null;
}
/**
* 向 textarea 写入值(兼容 React 受控组件)
* 直接 el.value = x 无法触发 React onChange,需用原生 setter + input 事件
* @param {HTMLTextAreaElement} el
* @param {string} value
*/
function setTextareaValue(el, value) {
const proto = el.constructor.prototype; // 取原型
const setter = Object.getOwnPropertyDescriptor(proto, 'value'); // 取原生 value setter
if (setter && setter.set) {
setter.set.call(el, value); // 用原生 setter 赋值
} else {
el.value = value; // 兜底直接赋值
}
el.dispatchEvent(new Event('input', { bubbles: true })); // 触发 input 事件
el.dispatchEvent(new Event('change', { bubbles: true })); // 触发 change 事件
}
/**
* 向 contenteditable 富文本编辑器写入值
* 使用 execCommand('insertText') 方式,兼容 Lexical/Slate/ProseMirror 等
* @param {HTMLElement} el - contenteditable 元素
* @param {string} value - 要填入的文本
*/
function setContentEditableValue(el, value) {
el.focus(); // 聚焦编辑器
// 全选后用 insertText 替换,确保 Lexical 等框架能感知变更
document.execCommand('selectAll', false, null); // 全选现有内容
document.execCommand('insertText', false, value); // 插入新文本(替代选中内容)
el.dispatchEvent(new Event('input', { bubbles: true })); // 触发 input 事件
// 光标移到末尾
const range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
/**
* 根据 inputType 选择填充方式并写入值
* @param {Element} el - 输入框元素
* @param {string} type - 'textarea' | 'contenteditable'
* @param {string} value - 要填入的内容
*/
function fillInput(el, type, value) {
if (type === 'textarea') {
setTextareaValue(el, value);
} else if (type === 'contenteditable') {
setContentEditableValue(el, value);
}
}
// #endregion DOMUtils
// ============================================================
// 区域:模型切换 ModelSwitcher
// 支持两种模式:直接点击型(DeepSeek)与下拉展开型(Kimi)
// ============================================================
// #region ModelSwitcher
/**
* 切换模型模式
* @param {object} adapter - 适配器配置
* @param {string} modelKey - 参数 m 的值
* @returns {Promise<boolean>} 是否执行了切换(已是当前模型返回 false)
*/
async function switchModel(adapter, modelKey) {
if (!adapter.modelConfig) return false; // 不支持模型切换
const config = adapter.modelConfig[modelKey];
if (!config) { // 未知模型 key
console.warn(`[AI Query Filler] 未知模型参数 m=${modelKey},支持: ${Object.keys(adapter.modelConfig).join('/')}`);
return false;
}
// ---- 下拉式切换(Kimi 模式):有 trigger 先点展开 ----
if (adapter.modelTrigger) {
const trigger = await waitForElement([adapter.modelTrigger], 5000);
if (!trigger) {
console.warn(`[AI Query Filler] 未找到模型切换按钮: ${adapter.modelTrigger}`);
return false;
}
// 检查是否已是目标模型
if (adapter.modelIsActive && adapter.modelIsActive(trigger, modelKey, config)) {
console.log(`[AI Query Filler] 模型已是 ${modelKey},无需切换`);
return false;
}
// 点击展开下拉
trigger.click();
// 等待下拉项出现
await new Promise(r => setTimeout(r, 300));
// 在下拉中找到并点击目标选项
const option = adapter.modelFindOption
? adapter.modelFindOption(config)
: document.querySelector(config.selector);
if (option) {
option.click();
console.log(`[AI Query Filler] 已切换模型: ${modelKey}`);
return true;
} else {
console.warn(`[AI Query Filler] 未找到模型选项: ${config.optionText || modelKey}`);
// 点击其他位置收起下拉
trigger.click();
return false;
}
}
// ---- 直接点击型(DeepSeek 模式):直接点击目标按钮 ----
const btn = await waitForElement([config.selector], 5000);
if (!btn) {
console.warn(`[AI Query Filler] 未找到模型按钮: ${config.selector}`);
return false;
}
// 检查激活态
if (adapter.modelIsActive && adapter.modelIsActive(btn, modelKey, config)) {
console.log(`[AI Query Filler] 模型已是 ${modelKey},无需切换`);
return false;
}
btn.click();
console.log(`[AI Query Filler] 已切换模型: ${modelKey}`);
return true;
}
// #endregion ModelSwitcher
// ============================================================
// 区域:主流程 Main
// 编排:解析参数 → 匹配适配器 → 切换模型 → 填充输入框
// ============================================================
// #region Main
/** 仅填充一次的标志位 */
let filled = false;
/**
* 主入口
*/
async function main() {
// 1. 读取参数:q=问题, m=模型
const question = getQueryParam('q');
if (!question) return; // 无问题参数则静默退出
const modelKey = getQueryParam('m'); // 模型参数可选
// 2. 匹配当前站点的适配器
const adapter = adapters.find(a => window.location.href.startsWith(a.match.replace(/\*$/, '')));
if (!adapter) return; // 无匹配适配器则退出
console.log(`[AI Query Filler] 命中适配器: ${adapter.id}, q=${question.slice(0, 30)}..., m=${modelKey || '(默认)'}`);
// 3. 切换模型(若指定了 m 参数)
if (modelKey) {
await switchModel(adapter, modelKey);
// 切换模型后输入框可能被重建,稍等一下
await new Promise(r => setTimeout(r, 500));
}
// 4. 等待输入框出现
const input = await waitForElement(adapter.inputSelector);
if (!input) {
console.warn('[AI Query Filler] 等待输入框超时');
return;
}
// 5. 填充问题内容(仅一次)
if (filled) return;
filled = true;
fillInput(input, adapter.inputType, question); // 根据类型填充
input.focus(); // 聚焦输入框
console.log('[AI Query Filler] 已填充问题到输入框(未提交)');
}
// #endregion Main
// 启动
main().catch(err => console.error('[AI Query Filler] 异常:', err));
})();