ai对话历史快捷查询追溯
// ==UserScript==
// @name ai对话历史快捷查询追溯
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description 解决单个对话太长,难以追溯之前的问答的问题,提供对历史问题的快捷寻找
// @author 口吃者
// @match https://yuanbao.tencent.com/*
// @match https://tongyi.aliyun.com/*
// @match https://kimi.moonshot.cn/*
// @match https://chat.deepseek.com/*
// @require https://scriptcat.org/lib/2691/1.0.0/sweetalert2.all.min-11.15.10.js
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const questionSelectorOption = {
'yuanbao': '[data-conv-speaker="human"]',
'tongyi': '.questionItem--dS3Alcnv',
'kimi': '.user-content',
'deepseek': '.fbb737a4'
};
const url = window.location.href;
var questionSelector;
if(url.includes('yuanbao')){
questionSelector = questionSelectorOption['yuanbao'];
}else if(url.includes('tongyi')){
questionSelector = questionSelectorOption['tongyi'];
}else if(url.includes('kimi')){
questionSelector = questionSelectorOption['kimi'];
}else if(url.includes('deepseek')){
questionSelector = questionSelectorOption['deepseek'];
}
window.addEventListener('load', addPanel);
// 辅助函数:平滑滚动到元素中心点
function scrollToElement(element) {
return new Promise((resolve) => {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 等待滚动动画完成
setTimeout(resolve, 500); // 假设滚动动画持续时间不超过500ms
});
}
/**
* 根据选择器提取文本内容并裁剪到最大字符限制,并添加序号前缀
* @param {string} selector - CSS选择器字符串
* @param {number} maxChars - 每个提示项的最大字符数
* @returns {string[]} 裁剪后的提示项数组(带序号前缀且去除多余空格)
*/
function getTrimmedSuggestions(selector, maxChars) {
// 参数验证
if (typeof selector !== 'string' || selector.trim() === '') {
throw new Error('Invalid selector: must be a non-empty string');
}
if (typeof maxChars !== 'number' || maxChars < 1) {
throw new Error('Max chars must be a positive integer ≥1');
}
// 获取所有匹配元素并裁剪文本
const elements = document.querySelectorAll(selector);
const suggestions = [];
elements.forEach(element => {
// 新增:处理换行符和多余空格
const rawText = element.textContent;
const cleanedText = rawText
.replace(/[\r\n]+/g, ' ') // 替换换行符为空格
.replace(/\s+/g, ' ') // 合并连续空格
.trim(); // 移除首尾空格
if (cleanedText.length === 0) return; // 跳过空文本
// 裁剪逻辑
let trimmedText = cleanedText;
if (cleanedText.length > maxChars) {
trimmedText = cleanedText.slice(0, maxChars);
// 可选:添加省略号(根据需求选择是否开启)
// trimmedText += '...';
}
suggestions.push(trimmedText);
});
// 添加序号前缀
const result = [];
const len = suggestions.length;
for (let i = 0; i < len; i++) {
const index = -(len - 1 - i);
const prefix = `${index}:`; // 中文冒号
result.push(`${prefix}${suggestions[i]}`);
}
return result;
}
function addPanel() {
function genButton(text, foo, id, fooParams = {}) {
let b = document.createElement('button');
b.textContent = text;
b.style.verticalAlign = 'inherit';
// 使用箭头函数创建闭包来保存 fooParams 并传递给 foo
b.addEventListener('click', () => {
foo.call(b, ...Object.values(fooParams)); // 使用 call 方法确保 this 指向按钮对象
});
if (id) { b.id = id };
return b;
}
function changeRangeDynamics() {
const value = parseInt(this.value, 10);
// 只能通过 DOM 方法改变
document.querySelector('#swal-range > output').textContent = value;
}
async function openPanelFunc() {
var swalRangeValue = 0;
// const promptArray = ["JavaScript框架", "React组件", "Vue开发"];
const { value: formValues } = await Swal.fire({
position: "center-end",
draggable: true,
backdrop: false,
title: "<strong>Jump Location</strong>",
showCancelButton: true,
cancelButtonText: 'Cancel',
confirmButtonText: 'Jump',
//class="swal2-range" swalalert框架可能会对其有特殊处理,导致其内标签的id声明失效
html: `
<input id="swal-input1" class="swal2-input" placeholder="输入关键词...">
<div id="suggestion-list" class="suggestion-list"></div>
<div class="swal2-range" id="swal-range" style="display: flex;">
<input type="range" min="-25" max="0" step="1" value="0">
<output>${swalRangeValue}</output>
</div>
`,
focusConfirm: false,
didOpen: () => {
const swalRange = document.querySelector('#swal-range input');
const input = document.getElementById("swal-input1");
const suggestionList = document.getElementById("suggestion-list");
const promptArray = getTrimmedSuggestions(questionSelector, 16);
// 绑定输入事件
input.addEventListener("input", function () {
const query = this.value.trim().toLowerCase();
const filtered = query
? promptArray.filter((item) =>
item.toLowerCase().includes(query)
)
: promptArray;
// 渲染提示项(无需单独绑定事件)
suggestionList.innerHTML = filtered
.map((item) => `<div class="suggestion-item">${item}</div>`)
.join("") || "<div class='no-result'>无匹配项</div>";
// suggestionList.style.display = filtered.length > 0 ? "block" : "none";
suggestionList.style.display = "block";
});
// 使用事件委托处理点击
suggestionList.addEventListener("click", (e) => {
const target = e.target;
if (target.classList.contains("suggestion-item")) {
e.preventDefault();
input.value = target.textContent;
const serialNumber = target.textContent.split(':')[0];
//同步更新序号选择器
document.querySelector('#swal-range > output').textContent = parseInt(serialNumber);
suggestionList.style.display = "none";
}
});
// 移除全局点击事件(原代码中的全局监听器已优化)
// 不需要额外处理,因为事件委托已足够
swalRange.addEventListener('input', changeRangeDynamics);
},
willClose: () => {
// 在关闭前清除事件监听器以防止内存泄漏
const swalRange = document.querySelector('#swal-range input');
swalRange.addEventListener('input', changeRangeDynamics);
},
preConfirm: () => {
swalRangeValue = document.querySelector('#swal-range > output').textContent;
return [
parseInt(swalRangeValue)
];
}
});
if (formValues) {
const humanArray = document.querySelectorAll(questionSelector);
const targetSerial = humanArray.length + formValues[0] - 1;
if(targetSerial < 0){
return;
}
scrollToElement(humanArray[targetSerial]);
}
}
let myButton = genButton('History', openPanelFunc, 'History');
document.body.appendChild(myButton);
var css_text = `
#History {
position: fixed;
color: #FF6B35; /* 橙色活力主题主色 */
top: 70%;
right: -20px; /* 初始右侧隐藏 */
transform: translateY(-50%);
z-index: 1000;
padding: 10px 24px;
border-radius: 5px;
cursor: pointer;
border: 0;
background-color: white;
box-shadow: rgba(0 0 0 / 5%) 0 0 8px;
letter-spacing: 1.5px;
text-transform: uppercase;
font-size: 9px;
transition: all 0.5s ease;
}
#History:hover {
right: 0%; /* 鼠标悬停时完整显示 */
letter-spacing: 3px;
background-image: linear-gradient(to top, #FFD700 0%, #FFA080 100%); /* 橙色渐变 */
box-shadow: rgba(255, 107, 0, 0.7) 0px 7px 29px 0px; /* 橙色阴影 */
}
#History:active {
letter-spacing: 3px;
background-image: linear-gradient(to top, #FFD700 0%, #FFA080 100%);
box-shadow: rgba(255, 107, 0, 0.5) 0px 0px 0px 0px;
transition: 100ms;
}
/* 提示列表容器 */
.suggestion-list {
margin-top: 8px;
padding: 8px;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 150px;
overflow-y: auto;
display: none;
}
/* 单个提示项 */
.suggestion-item {
display: flex;
align-items: center;
padding: 6px 12px;
cursor: pointer; /* 默认显示为可点击 */
transition: background-color 0.3s ease; /* 平滑过渡效果 */
}
/* 悬停时的样式 */
.suggestion-item:hover {
background-color: #f0f0f0; /* 浅灰色背景 */
cursor: pointer; /* 强调点击效果 */
}
/* 无匹配项的样式 */
.no-result {
padding: 6px 12px;
color: #888;
text-align: center;
}
`;
GMaddStyle(css_text);
}
function GMaddStyle(css) {
var myStyle = document.createElement('style');
myStyle.textContent = css;
var doc = document.head || document.documentElement;
doc.appendChild(myStyle);
}
})();