豆爸:GPT作为关联学习的工具-双链、知识体系
更新日志
- 更新了文档 2023-09-30 21:40:32
- 添加了快捷键,Ctrl + G 对选中的文字发送。 2023-10-01 02:01:24
- 合并了快捷键,仅保留 Ctrl + C 实现所有功能。2023-10-01 03:12:41
- 更新了配套的常青笔记prompt到1.0 2023-10-01 04:25:08
- 添加一个原子笔记的prompt 2023-10-01 04:50:23
- 这里不更新了。功能整合到南瓜博士的插件里面了。 EasyFill 中了 2023-10-06 23:23:56
- https://github.com/1766left/EasyFill/tree/master
- 我自己比较习惯快捷键,也有在南瓜博士的版本上修改一些自己习惯偏好的快捷方式。
- 可以点这里查看
简介:
这个工具主要用于将关键词转化成链接,减少手动输入的麻烦,适用于需要频繁与网页交互的场景。比如,在某些实时聊天应用或者网页版的自动问答系统中,这个工具能大大提高操作效率。
前置条件:
需要您在prompt中让GPT生成能识别的关键词。 比如 :[[常青笔记标题]] 或者 **加粗字体**
视频演示:
功能:
- 生成链接: 对网页内所有包含
[[示意文本]]
和加粗字体
的文本进行识别,并将它们转换为可以点击的链接。 - 快速发送: 当这些生成的链接被点击后,自动发送到GPT。
- 支持热键:
- 支持 Ctrl + C 对现有页面的词汇转换成链接。对选中的文本发送到GPT
插件使用方法
流程
- 安装浏览器插件:Tampermonkey
- 支持到 浏览器 chrome 、edge、ARC
- https://chrome.google.com/webstore/detail/tampermonkey-beta/gcalenpjmijncebpfijmoaglllgpjagf
- 打开GPT官网
- 点击插件图标,新建
- 完整的替换我提供个文件。
- 保存后关闭
- GPT官网内刷新下
- 图标上出现了数字1说明生效
- 如果没有看到效果,再安装好之后 ,按一下快捷键 Ctrl + C 试试
JS代码-文本转链接插件
将以下内容黏贴到 Tampermonkey中:
// ==UserScript==
// @name GPT-关键词链接
// @namespace http://tampermonkey.net/
// @version 0.2
// @description try to take over the world!
// @author You
// @match https://chat.openai.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 作者:豆爸
// 微信:ttmouse
// 更新日期:2023-10-01
// 版本:1.0
// 功能:
// 对页面内所有的 [[文字]] 和 **文字** 添加可以点击的链接。
// 点击链接后,将链接文本添加到输入框并自动提交发送。达到快速发送关键词,减少输入的目的。
// 支持热键 Ctrl + C,转化页面内所有匹配的文本。
// 支持热键 Ctrl + C,转化选中的文本。
let isUpdating = false;
let rerunTimeout;
let intervalID;
let shouldContinue = true;
// 点击事件处理器
async function clickHandler(event) {
event.preventDefault();
console.log('执行 点击链接处理');
const inputElement = document.getElementById('prompt-textarea');
console.log('获取 输入框');
inputElement.value = this.getAttribute("data-text");
console.log('将链接文本添加到输入框');
const inputEvent = new Event('input', { 'bubbles': true });
console.log('创建 input 事件');
inputElement.dispatchEvent(inputEvent);
console.log('触发 input 事件');
await new Promise(resolve => setTimeout(resolve, 50));
console.log('50ms 延时完成');
const sendButton = document.querySelector('[data-testid="send-button"]');
console.log('获取 发送按钮');
if (sendButton) {
console.log('点击 发送按钮');
sendButton.click();
startListening();
console.log('开启监听');
}
}
// 处理元素
function processElement(element) {
//console.log('尝试处理元素');
if (isUpdating) {
console.log('正在更新,跳过');
return;
}
let innerHTML = element.innerHTML;
// 处理[[ ]] 符号
const bracketRegex = /\[\[(.*?)\]\]/g;
innerHTML = innerHTML.replace(bracketRegex, function(match, capture) {
return `<a class="custom-link" data-text="${capture}">${capture}</a>`;
});
// 处理 <strong>标签
const strongRegex = /<strong>(.*?)<\/strong>/g;
innerHTML = innerHTML.replace(strongRegex, function(match, capture) {
return `<a class="custom-link strong-link" data-text="${capture}">${capture}</a>`;
});
element.innerHTML = innerHTML;
// console.log('替换 HTML 内容完成');
// 给新生成的链接添加事件监听
const customLinks = element.querySelectorAll('.custom-link');
customLinks.forEach(link => {
link.addEventListener('click', clickHandler);
});
// console.log('添加点击事件完成');
}
// 给 'send-button' 按钮添加点击事件
const addSendButtonListener = () => {
console.log('开始找 send-button 按钮');
const sendButton = document.querySelector('[data-testid="send-button"]');
if (sendButton) {
console.log('找到 send-button 按钮,添加点击事件');
sendButton.addEventListener('click', () => {
console.log('send-button 按钮被点击,开启监听');
startListening();
// 先找到父级对象
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
parentElements.forEach(parent => {
// 在父级对象下面找特定的子元素
const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
chatRecordElements.forEach(processElement);
});
});
}
};
// 给 "Regenerate" 文本的按钮点击事件
const observeRegenerateButton = () => {
console.log('开始找 Regenerate 按钮');
const allButtons = document.querySelectorAll('button');
const regenerateButtonElement = Array.from(allButtons).find(el => el.textContent.includes("Regenerate"));
if (regenerateButtonElement) {
console.log('找到 Regenerate 按钮,添加点击事件');
regenerateButtonElement.addEventListener('click', () => {
console.log('Regenerate 按钮被点击,开启监听');
startListening();
});
}
};
// 这个部分是用来检测GPT是否在更新的
function checkUpdateStatus() {
if (!shouldContinue) return;
console.log('检查更新状态');
const allButtons = document.querySelectorAll('button');
const stopGeneratingElement = Array.from(allButtons).find(el => el.textContent.includes("Stop generating"));
if (stopGeneratingElement && !isUpdating) {
console.log('内容正在更新');
isUpdating = true;
} else if (!stopGeneratingElement && isUpdating) {
console.log('内容更新完成,准备添加链接');
isUpdating = false; // 更新状态设置为false
if (rerunTimeout) {
clearTimeout(rerunTimeout); // 清除延时
console.log('清除之前的延时');
}
// 设置延时,等待2秒
rerunTimeout = setTimeout(() => {
console.log('延时结束,开始添加链接');
// 先找到父级对象
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
parentElements.forEach(parent => {
// 在父级对象下面找特定的子元素
const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
chatRecordElements.forEach(processElement);
});
console.log('完成 添加链接');
stopListening();
}, 300);
}
}
function startListening() {
shouldContinue = true;
intervalID = setInterval(checkUpdateStatus, 1000);
console.log('持续监听 - 已开启');
}
function stopListening() {
shouldContinue = false;
clearInterval(intervalID);
console.log('持续监听 - 已停止');
addSendButtonListener()
observeRegenerateButton();
}
function fristrun() {
console.log('开始首次 链接转换');
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
parentElements.forEach(parent => {
// 在父级对象下面找特定的子元素
const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
chatRecordElements.forEach(processElement);
});
console.log('完成首次 添加链接');
addSendButtonListener()
observeRegenerateButton();
}
// 监听快捷键 Ctrl + C
document.addEventListener('keydown', async function(event) {
if (event.ctrlKey && !event.metaKey) {
if (event.code === 'KeyC') {
console.log('Ctrl + C 快捷键触发成功');
let selectedText = window.getSelection().toString();
// 检查是否有选中的文本
if (selectedText.length > 0) {
console.log('有选中的文本');
// 获取输入框元素
let inputElement = document.getElementById('prompt-textarea');
// 设置输入框的值为选中的文本
inputElement.value = selectedText;
// 触发 input 事件
const inputEvent = new Event('input', { 'bubbles': true });
inputElement.dispatchEvent(inputEvent);
// 延时 50ms
await new Promise(resolve => setTimeout(resolve, 50));
// 获取并点击 'send-button'
const sendButton = document.querySelector('[data-testid="send-button"]');
if (sendButton) {
sendButton.click();
startListening();
}
} else {
// 如果没有选中文本,执行其他逻辑
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
parentElements.forEach(parent => {
const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
chatRecordElements.forEach(processElement);
});
observeRegenerateButton();
addSendButtonListener();
}
}
}
});
// 监听键盘快捷键事件
document.addEventListener('keydown', async function(event) {
// 检测是否按下 Ctrl + G
if (event.code === 'KeyG' && event.ctrlKey && !event.metaKey) {
// 防止触发默认的复制行为
// event.preventDefault();
console.log('Ctrl+G 快捷键被按下');
// 获取选中的文本
let selectedText = window.getSelection().toString();
console.log('获取选中的文本');
// 获取输入框元素
let inputElement = document.getElementById('prompt-textarea');
console.log('获取输入框');
// 设置输入框的值为选中的文本
inputElement.value = selectedText;
console.log('设置输入框的值');
// 触发 input 事件
const inputEvent = new Event('input', { 'bubbles': true });
inputElement.dispatchEvent(inputEvent);
console.log('触发 input 事件');
// 延时 50ms
await new Promise(resolve => setTimeout(resolve, 50));
console.log('50ms 延时完成');
// 获取并点击 'send-button'
const sendButton = document.querySelector('[data-testid="send-button"]');
if (sendButton) {
console.log('获取发送按钮');
sendButton.click();
console.log('点击发送按钮');
// 开启监听
startListening();
console.log('开启监听');
}
}
});
fristrun();
// 默认开启监听
startListening();
addSendButtonListener()
observeRegenerateButton();
})();
EasyFill的基础上修改的插件,右键菜单,悬浮菜单,快捷键。
更新时间:2023-10-17 18:47:08
// ==UserScript==
// @name EasyFill
// @namespace http://easyfill.tool.elfe/
// @version 0.4
// @description 超级方便的 GPT 对话助手,通过划选或点击,把内容填充到预置 prompt 模版直接发送。支持多个功能组设置。
// @author Elfe & ttmouse & GPT
// @match https://chat.openai.com/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAAAAABWESUoAAABX2lDQ1BJQ0MgUHJvZmlsZQAAeJxtkLFLAmEYxh/NEI6LFCIaghyiycLOgla1qMDh0ARrO8/rDPT8uLuQtoJaQ6ihtrClsakWh9amgqAhotr6AyIXk+v9vEqtvo+X58fD+768PIBXVBgr+gCUDNtMLcZD2dW1kP8VArwYQBCColosJstJasG39r7GPTxc7yb5ruB+9rop1dM7lef4WOD86G9/zxPymqWSflBJKjNtwBMhlis247xNPGTSUcSHnHWXzzjnXK63e1ZSCeJb4oBaUPLEL8ThXJevd3GpuKl+3cCvFzUjkyYdphrFPBaQpB9CBhJmMU21RBn9PzPTnkmgDIYtmNiAjgJsmo6Rw1CERrwMAyqmECaWEKGK8qx/Z9jxyjVg7h3oq3a83DFwuQeMPHS88RNgcBe4uGGKqfwk62n4rPWo5LIYB/qfHOdtAvAfAK2q4zRrjtM6pf2PwJXxCXhkY9XHGXyzAAAAVmVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADkoYABwAAABIAAABEoAIABAAAAAEAAAEgoAMABAAAAAEAAAEgAAAAAEFTQ0lJAAAAU2NyZWVuc2hvdPDFp1oAAABUSURBVHic3VFBDgAgCMLW/79M1wiX1TFuAhOcwK/gPLTKnRhYGFRH38u2gQAQMxOmK6MjTU4LUHpkJWWLlAw/oi65RhQlPeHpWduMO/sZ1l84+QUG1/URFizO5xoAAAAASUVORK5CYII=
// @grant none
// ==/UserScript==
// Copyright (c) 2023 ElfeXu (Xu Yanfei)
// Licensed under the MIT License.
/*
////////////功能介绍//////////
功能:
1. 将选中的文本,叠加自定义的Prompt之后发送的GPT。
2. 特定格式的文本,可以自动转换成可以点击的链接。
3. 可以将不同的Prompt组合基于不同的场景存储为一个配置组,支持在不同的配置组中切换。比如:学英语的场景,猜谜游戏场景。
4. 能识别是否打开了原有的聊天内容重新编辑,如果打开了,会在打开了的区块内替换发送。需要先手动点击编辑按钮。
右键菜单激活方式:
1. 双击
2. 选文本的时候按住 command 键
快捷键:
右键菜单开启的时候:1-9 执行对应菜单选项
选中文字的时候:1-9 执行对应菜单选项
EF 打开设置
GG 切换下一组设置
G1-9 切换固定位置的设置组
*/
////////////////////////// 初始化 //////////////////////////
const setting_usage_text = `使用说明
通过 🪄 分隔按钮
📖 之后的是直接在原文替代成链接的内容
🪄🪄🪄🪄🪄🪄🪄🪄
功能一
这里是预设的 prompt ,{__PLACE_HOLDER__} 里的内容会被你鼠标选中的文字替代掉。
🪄🪄🪄🪄🪄🪄🪄🪄
功能二
点击菜单文字可以直接发送,点击右边会把 prompt 填充到输入框,可以编辑后再发送。
🪄🪄🪄🪄🪄🪄🪄🪄
CLICK 示范
通过要求 GPT 以特定格式生成内容,可以将内容转化成链接,点击即直接发送。例如
请给我五个和有水关的英文单词,用两个方括号 [[]] 来标记。
再用列表的方式给出三个和水有关的节日,节日名称写在 💦 💦 之间
📖📖📖📖📖📖📖📖
\[\[(.*?)\]\]
请帮我解释一下{__PLACE_HOLDER__}这个词的意思
📖📖📖📖📖📖📖📖
💦(.*?)💦
请帮我详细介绍一下{__PLACE_HOLDER__}。
`
const setting_new_setting_text = `新功能组名称
这里可以填写功能组使用说明
通过 = 分隔按钮
🪄🪄🪄🪄🪄🪄🪄🪄
第一行是按钮名称
第二行开始是prompt。{__PLACE_HOLDER__} 里的内容会被你鼠标选中的文字替代掉。
🪄🪄🪄🪄🪄🪄🪄🪄
第二个功能
第二个prompt
prompt多长都没关系
各种奇怪字符也都可以用
只根据连续八个=来分隔功能
📖📖📖📖📖📖📖📖
\[\[(.*?)\]\]
在按钮之后可以用八个📖分隔,带上点击直接发送的内容。
第一行是正则匹配,后面是模版。匹配到的内容会替代掉{__PLACE_HOLDER__}中的内容然后被直接发送。
📖📖📖📖📖📖📖📖
<strong>(.*?)<\/strong>
可以设置多个直接点击项,每一项用不同的正则匹配即可。让 GPT 输出不同格式的内容,定义成不同的后续行动。
{__PLACE_HOLDER__}
`;
const default_setting_texts = [
`英语练习
先点启动,再贴大段文章,然后需要干啥就选中了文字点啥功能
🪄🪄🪄🪄🪄🪄🪄🪄
启动
你是我的英语老师,我需要你陪我练习英语,准备托福考试。
请**用英语和我对话**,涉及英语例句、题目和话题探讨时请用托福水平的书面英语,但在我明确提出需要时切换到中文。
为了让我的学习更愉悦,请用轻松的语气,并添加一些 emoji。
接下来我会给你一篇英文文章,请记住文章,然后我会向你请求帮助。
如果你理解了,请说 Let's begin!
🪄🪄🪄🪄🪄🪄🪄🪄
英译中
请帮我把下面这段话翻译直译成中文,不要遗漏任何信息。
然后请判断文字是否符合中文表达习惯,如果不太符合,请重新意译,在遵循愿意的前提下让内容更通俗易懂。
输出格式应该是
直译:直译的内容
---
(如果有必要的话)意译:意译的内容
待翻译的内容:
'''
{__PLACE_HOLDER__}
'''
🪄🪄🪄🪄🪄🪄🪄🪄
中译英
请帮我用最地道的方式帮我把下面这段话翻译成英文。
待翻译的内容:
'''
{__PLACE_HOLDER__}
'''
🪄🪄🪄🪄🪄🪄🪄🪄
学单词
'''
{__PLACE_HOLDER__}
'''
请帮我学习这个单词
1. 请给出单词的音标、词性、中文意思、英文意思
2. 如果我们前面的讨论中出现过这个单词,请结合它的上下文,重点讲解在上下文中单词的意思和用法
3. 请给出更多例句
4. 如果有容易混淆的单词,请给出对比
🪄🪄🪄🪄🪄🪄🪄🪄
深入解释
我不太理解这段文字的具体含义,能否结合上下文,给我一个更深入的中文解释?
解释时请着重讲解其中有难度的字词句。
如果有可能,请为我提供背景知识以及你的观点。
'''
{__PLACE_HOLDER__}
'''
🪄🪄🪄🪄🪄🪄🪄🪄
封闭题
请对下面这段文字,按照托福阅读理解的难度,用英文为我出三道有标准答案的问答题。
请等待我回答后,再告诉我标准答案,并加以解释。
'''
{__PLACE_HOLDER__}
'''
🪄🪄🪄🪄🪄🪄🪄🪄
开放题
请对下面这段文字,按照托福口语和作文的难度,用英文为我出一道开放题,我们来进行探讨。
'''
{__PLACE_HOLDER__}
'''
`,
setting_usage_text
];
const LSID_SETTING_TEXTS = 'setting_texts_v0.4';
const LSID_SETTING_CURRENT_INDEX = 'setting_current_index_v0.4';
////////////////////////// CSS //////////////////////////
const style = `
.settings-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);;
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.settings-content {
background-color: #161616;
color: #535e5e;
padding: 20px;
width: 50%;
height: 80%;
overflow-y: auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 20px;
}
.menuItem.highlight {
background-color: #d0d0cd;
color: #000;
font-weight: 700;
}
.settings-dropdown {
outline: none;
border: 0px;
}
.settings-textarea {
width: 100%;
height: calc(100% - 60px);
resize: vertical;
background-color: #212121;
color: #cacaca;
border-radius: 5px;
border: 0px;
padding: 18px 18px;
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 0.5px, rgba(0, 0, 0, 0.024) 0px 0px 5px, rgba(0, 0, 0, 0.05) 0px 1px 2px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.3);
display: none;
align-items: center;
justify-content: center;
z-index: 1000; // 确保模态窗口在页面的最上方
}
.modal-content {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 0px 15px rgba(0,0,0,0.2);
}
.settings-submit {
background-color: #857c5d;
color: #fff;
padding: 8px 18px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.settings-submit:hover {
background-color: #93B1A6;
}
.settings-submit:disabled {
background-color: #B4B4B3; /* 灰色背景 */
color: #808080; /* 深灰色文字 */
cursor: not-allowed; /* 禁用的光标样式 */
}
#contextMenu {
display: none;
position: absolute;
}
.page-menu {
position: absolute;
top: 470px;
right: 9px;
background-color: #7575751a;
color: #676767;
border-radius: 5px;
padding: 5px 0px;
width: fit-content;
font-size: 15px;
display: inline-block;
}
#searchContainer {
display: none;
flex-direction: column;
align-items: flex-start;
width: 100;
height: fit-content;
background-color: #f6f6f6;
border-radius: 10px;
color: #c6c6c6;
padding-bottom: 10px;
border: 1px solid #6565650f;
shadow: 0 0 transparent,0 0 transparent,var(--tw-shadow);
box-shadow: var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow);
}
#searchBoxTip {
display: flex;
font-size: 14px;
justify-content: center;
align-items: center;
padding: 5px;
color: #9e9e9e;
}
#searchBox {
position: relative;
caret-color: #000;
color: #4e4e4e;
top: 0px;
right: 0px;
border-radius: 10px;
padding: 10px;
font-weight: 700;
width: 400px;
height: 46px;
font-size: 24px;
display: block;
background-color: #25252500;
}
select {
background-position: right 0.5rem center;
background-repeat: no-repeat;
color: #fff;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
border-radius: 10px;
border: 1px solid #dedede;
print-color-adjust: exact;
background-color: #2a2a2a;
}
#searchBox:selected {
position: relative;
top: 0px;
right: 0px;
border-radius: 5px;
padding: 10px;
font-weight: 700;
width: 301px;
height: 46px;
font-size: 18px;
display: block;
background-color: #252525;
}
#searchBox:focus {
outline: none;
}
#searchBoxList {
position: relative;
color: #676767;
width: 100%;
font-size: 15px;
display: none;
}
.searchBoxListItem:hover {
background-color: ##e4e4e21f;
}
.searchBoxListItem.selected {
background-color: #b4ac99; /* 选中项的背景色 */
}
.searchBoxListItem {
color: #3f3f3f;
display: flex;
text-align: left;
padding: 10px;
font-weight: bold;
cursor: pointer;
}
#serachBoxListContainer {
width: auto;
display: inline-block;
background-color: #fff;
color: #000;
border-radius: 0.55em;
border: 0px;
padding: 5px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 0px 0.5px, rgba(0, 0, 0, 0.1) 0px 2px 5px, rgba(0, 0, 0, 0.05) 0px 3px 3px;
border-bottom: 1px solid #f0f0f0; /* 浅灰色的线条 */
padding: 5px 0;
}
#groupMenu button,
#pageMenu button {
border: none;
background: none;
text-align: left;
padding: 5px 10px;
margin: 0;
width: auto;
white-space: nowrap; /* 防止换行 */
border-radius: 5px;
transition: background-color 0.3s ease;
}
#groupMenu {
position: absolute;
top: 81px;
right: 10px;
background-color: #7575751a;
color: #676767;
border-radius: 5px;
width: fit-content;
font-size: 15px;
display: inline-block;
}
.groupMenuContainer {
width: auto;
display: inline-block;
border-radius: 0.55em;
padding: 5px;
padding: 5px 0;
}
#pageMenu button:nth-child(2){
/* display: none !important; */
height: 100%;
width: 38px;
padding: 0px;
}
.menu-title {
display: flex;
align-items: center;
justify-content: center;
padding: 5px 0 !important;
font-weight: bold;
}
.groupMenuTitle,
.pageMenuTitle {
display: flex;
align-items: center;
justify-content: center;
padding: 5px 0 !important;
font-weight: bold;
cursor: pointer;
}
#menuContainer {
width: auto;
display: inline-block;
background-color: #fff;
color: #000;
border-radius: 0.55em;
border: 0px;
padding: 5px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 0px 0.5px, rgba(0, 0, 0, 0.1) 0px 2px 5px, rgba(0, 0, 0, 0.05) 0px 3px 3px;
border-bottom: 1px solid #f0f0f0; /* 浅灰色的线条 */
padding: 5px 0;
}
#menuContainer div {
display: flex;
align-items: center;
width: auto;
border-bottom: 1px solid #f0f0f0;
padding: 2px 0;
margin: 0px;
}
/* 最后一个没有底部线条 */
#menuContainer div:last-child {
border-bottom: 1px solid #f0f0f000;
}
#menuContainer button {
border: none;
background: none;
text-align: left;
padding: 5px 10px;
margin: 0;
width: auto;
white-space: nowrap; /* 防止换行 */
border-radius: 5px;
transition: background-color 0.3s ease;
}
#menuContainer button:disabled {
height: 1px;
color: #c6c6c600;
padding: 0;
border-bottom: 1px solid #dddddd8c;
display: none;
}
/* 右键菜单选中的时候的颜色,配色采用GPT4紫色 */
#menuContainer div:not(:first-child):hover {
color: #9038fb;
display: flex;
align-items: center;
width: auto;
border-bottom: 1px solid #f0f0f0;
background-color: #ebd9ff5c;
padding: 2px 0;
margin: 0px;
}
#menuContainer button:disabled:hover {
background: none;
}
#menuContainer button img {
height: 20px; // 可以根据需要调整
width: 20px; // 可以根据需要调整
margin-right: 10px;
}
#menuContainer button:nth-child(2) {
text-align: center;
width: 38px;
padding: 0px;
}
/* 使链接看起来更像链接 */
.custom-link {
text-decoration: underline;
cursor: pointer; /* 当鼠标放上去时变成手的样子 */
position: relative; /* 为了定位 tooltip */
}
/* 提示(tooltip)样式 */
.custom-link:hover::after {
content: attr(data-text); /* 显示 data-text 的内容 */
position: absolute;
bottom: 100%; /* 出现在链接的上方 */
left: 0; /* 与链接的左边界对齐 */
width: max-content; /* 根据内容设置宽度 */
max-width: 300px; /* 设置最大宽度 */
white-space: normal; /* 允许文本换行 */
line-height: 18px;
max-height: 120px;
overflow: hidden;
background-color: #333; /* 背景色 */
color: white; /* 文字颜色 */
padding: 5px 10px; /* 内边距 */
border-radius: 4px; /* 边框圆角 */
font-size: 12px; /* 文字大小 */
z-index: 100; /* 保证 tooltip 出现在其他元素的上方 */
}
/* 切换设置组的提示样式 */
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #333;
color: white;
padding: 15px;
z-index: 1000;
border-radius: 5px;
display: none; /* 默认情况下,Toast 是隐藏的 */
}
`;
const iconSetting = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAANhJREFUOE+lk2sSgzAIhFkvVr2B8ULaCxlvoL1Y6JARB/Nqp/VfJCwfCwE1vnEcewlv23bUrqEWkGQAu8SZeaiJ3AScczuAVwjhADATUSQA8FzXdZmmaWHm2QpeApKsCa22NKYil4BFTrHPyo+UKNLZas45lrP3vuiNUmYt2Arab6kNpZQ74pMYixZ6SUQptc3/BURJKLqu69MRpQQ6xlsLiYlxlJ9MtPHqGK2Zp0/ZYt3GqHiNJZL3EDeTiA7v/ZDtgYjIT7u2dpWVRJMzAVv9p8eU4n/znN/5jr5xu8638gAAAABJRU5ErkJggg==';
const iconEdit = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAZJJREFUWEfFV9ttwzAMlCZrsoHlhZosZHmDOouZAY0SoGXJPMopmq98CLyHjhQdwz//4hX8cRwf0zQ9rtToJsDgRPTN4DHGZy+RbgIpJdLKmcS6rss8z4vHkS4Cop5BGUyc6HGji4CoJ6K7KNZX4iHiJqDV1+7dS8RFAA0eem5zyhOYMngtqzWBnPMpBkygLHpmtRBF2hMmUCvKJHQXMCARfYUQbgg4fAXe4HFhy3q5etOBYRhYzQ9SNKXE52D1kAOWelHiCd5ugp51gaeoJ3gwAbQo6lJNbDMDSv2Sc76fOYUSdRFAi15R3wwhWtSTkZaDhyvwFO1pu5JIk4A1yTxEz/KzI1DM9yXG+GqtWmhGrMduR0AsLVetkgSaEQv8EEI1djf1rVWrthEhYGYbagLS++WzG0LgpdM17+EM8EFRp1+z8tlFHibUkUMXSA5k4WRXfjcf/gaQ/93fAWYbqiDKfr+Bqp85mlH11Umo338NSkTP8qOj1jUIuF7nD1egg1gD1QB/QgBR8Mkz5kr2SbBarTe3GoEwVJGU+QAAAABJRU5ErkJggg==';
const iconSend = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAWNJREFUWEftVkESwiAMJH1Z/UHph9QPlf7A+rHGCSMOYoIJtXqRY4eym012AdyPF/wY3/0JfE2BYRh6ADgCwHWaplNq/e4EErBzridQRDzM87zsTqAEToAhhKeiP66ABLw7gXfAdwJLCOGQW3+zAkrgiAkA53wA47fWILIAPwbuEwRagBOB0gEmBQzAZLFouXKVDlARMABHjwPARWjrywBWCViAnXMLIp4p6aTquQFkCRiB6YxY2TiOJ0QkAuxSEbiDSxK+HJwOfQfORXA1ijUHWsAJjBvA6gzUSORyeu9RkSXsAKpcUBLJvey9p3axlnuKWyaAqi3gKiIi67ou6SrVtKmWgGYCOSkLeG0AVS2Q1KhZTpOAmxWgW02rhOSAZgVo8g02FB2wiUD8OZtuSQ0pATe1IHm/lJaStOu6Pp+P3Qhwd3uqKlejtq+5BVRp/rSWkpCIlE+wcm/zk0wRv6otfwI3KLv7Id7vNIsAAAAASUVORK5CYII=';
const styleElement = document.createElement('style');
styleElement.innerHTML = style;
document.head.appendChild(styleElement);
////////////////////////// Easy Fill functions //////////////////////////
// 100 初始化
let setting_texts = JSON.parse(localStorage.getItem(LSID_SETTING_TEXTS)) || default_setting_texts; // 用于存储设置的数组
let setting_current_index = localStorage.getItem(LSID_SETTING_CURRENT_INDEX) || 0; // 用于存储当前设置的序号
let current_setting_text = setting_texts[setting_current_index]; // 用于存储当前设置的内容
let actions = []; // 用于存储菜单项动作的数组
// 101 创建上下文菜单
const contextMenu = document.createElement('div'); // 创建一个 div 元素
const menuContainer = document.createElement('div'); // 创建一个 div 元素
contextMenu.id = 'contextMenu'; // 给这个 div 元素设置 id
menuContainer.id = 'menuContainer'; // 给这个 div 元素设置 id
const menuTitle = document.createElement('div');
menuTitle.classList.add('menu-title'); // 添加CSS类
menuTitle.innerHTML = setting_current_index; // 在菜单标题中显示当前的设置组序号
menuContainer.appendChild(menuTitle); // 把菜单标题添加到菜单容器中
contextMenu.appendChild(menuContainer); // 把菜单容器添加到右键菜单中
document.body.appendChild(contextMenu); // 把右键菜单添加到页面中
// 102 发送到GPT
async function sendToGPT(template, selectedText, sendDirectly) {
console.log("102 开始执行 sendToGPT 函数");
let placeholderPosition = template.indexOf('{__PLACE_HOLDER__}'); // 占位符的位置
let finalText = template.replace('{__PLACE_HOLDER__}', selectedText); // 把选中的文本替换到占位符中
let targetElement; // 目标元素
let saveSubmitButton; // Save & Submit 按钮
// 基于按钮文本来查找 "Save & Submit" 按钮
saveSubmitButton = Array.from(document.querySelectorAll('button')).find(el => el.textContent.trim() === "Save & Submit");
if (saveSubmitButton) {
console.log("102 识别到 Save & Submit 按钮");
let grandParentDiv = saveSubmitButton.closest('div.flex.flex-grow.flex-col.gap-3.max-w-full');
if (grandParentDiv) {
let textarea = grandParentDiv.querySelector('textarea');
if (textarea) {
targetElement = textarea;
}
}
}
if (!targetElement) {
console.log("102 未识别到 Save & Submit ,定位对象到默认聊天输入框");
targetElement = document.getElementById('prompt-textarea');
}
if (!targetElement) {
console.log("102 未识别到聊天框,定位到编辑框");
targetElement = document.querySelector('div[contenteditable="true"]'); // 定位到编辑框
}
targetElement.value = finalText;
// 如果是直接发送
if (sendDirectly) {
console.log("102 直接发送");
const inputEvent = new Event('input', { 'bubbles': true }); // 创建一个 input 事件
targetElement.dispatchEvent(inputEvent); // 触发 input 事件
await new Promise(resolve => setTimeout(resolve, 50)); // 等待 50 毫秒
if (saveSubmitButton) { // 如果找到了 Save & Submit 按钮,就点击它
saveSubmitButton.click();
} else { // 如果没有找到 Save & Submit 按钮,就点击发送按钮
const sendButton = document.querySelector('[data-testid="send-button"]');
if (sendButton) { // 如果找到了发送按钮,就点击它
sendButton.click();
simulateClick() // 底部。
}
}
}
// 如果不是直接发送
targetElement.focus(); // 聚焦到输入框
let cursorPosition; // 光标位置
if (placeholderPosition !== -1) { // 如果有占位符
if (selectedText) { // 如果有选中的文本
cursorPosition = placeholderPosition + selectedText.length; // 把光标放在占位符之后
} else { // 如果没有选中的文本
cursorPosition = placeholderPosition; // 把光标放在占位符之前
}
} else {
cursorPosition = targetElement.value.length; // 如果没有占位符,就把光标放在最后
}
targetElement.setSelectionRange(cursorPosition, cursorPosition); // 设置光标位置
// // 触发一个事件来让文本框高度适应
// const inputEvent = new Event('input', { bubbles: true });
// targetElement.dispatchEvent(inputEvent);
// 监听输入事件,当用户输入时,自动调整文本框高度
targetElement.addEventListener('input', function () {
this.style.height = 'auto'; // 重置高度,以便能正确计算新高度
this.style.height = (this.scrollHeight) + 'px'; // 设置文本框高度为内容的高度
});
// 初始触发一次输入事件,以确保文本框在加载页面时就适应内容高度
targetElement.dispatchEvent(new Event('input'));
console.log("102 sendToGPT 文本植入完成");
}
// 103 创建单个菜单项
function createMenuItem(index, label, icon1, icon2, action1, action2) {
// console.log("103 创建菜单项: " + label);
const menuItem = document.createElement('div'); // 创建一个 div 元素
menuItem.style.display = 'flex';
menuItem.style.alignItems = 'center';
const leftPart = document.createElement('button'); // 创建左边的按钮
leftPart.style.flex = '0.8';
leftPart.style.display = 'flex';
leftPart.style.alignItems = 'center';
// 在这里添加 数字序号
const textLabel = document.createElement('span'); // 创建一个 span 元素
textLabel.innerHTML = index + '. ' + label; // 在 span 元素中显示菜单项的文本
leftPart.appendChild(textLabel); // 把 span 元素添加到 button 元素中
if (action1 == null) { // 如果没有动作,就禁用这个按钮
leftPart.disabled = true;
} else {
leftPart.onclick = () => { // 如果有动作,就在点击时执行动作
action1();
hideContextMenu(); // 执行动作后隐藏右键菜单
};
actions.push(action1); // 记录这个动作
// console.log("103 添加了一个新动作,现在的 actions 数组: ", actions);
}
menuItem.appendChild(leftPart); // 把 button 元素添加到 div 元素中
const rightPart = document.createElement('button'); // 创建右边的按钮
if (icon2 !== null) { // 如果有第二个图标,就显示第二个图标
rightPart.style.flex = '0.2';
rightPart.style.display = 'flex';
rightPart.style.alignItems = 'center';
// 创建一个SVG元素并设置属性
const rightIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
rightIcon.setAttribute("width", "16");
rightIcon.setAttribute("height", "16");
rightIcon.setAttribute("class", "icon");
rightIcon.setAttribute("viewBox", "0 0 1024 1024");
// 创建一个`path`元素并设置SVG路径数据
const pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathElement.setAttribute("d", "M896 128.426667H128c-47.146667 0-85.333333 38.186667-85.333333 85.333333V384h85.333333V212.906667h768v598.613333H128V640H42.666667v171.093333c0 47.146667 38.186667 84.48 85.333333 84.48h768c47.146667 0 85.333333-37.546667 85.333333-84.48v-597.333333c0-47.146667-38.186667-85.333333-85.333333-85.333333zM469.333333 682.666667l170.666667-170.666667-170.666667-170.666667v128H42.666667v85.333334h426.666666v128z");
pathElement.setAttribute("fill", "#5D5D5D"); // 设置图标颜色
// 图标悬停的时候颜色变成紫色,尺寸会放大5%,非悬停的时候颜色变成灰色,
rightIcon.onmouseover = () => {
pathElement.setAttribute("fill", "#9038fb");
rightIcon.style.transform = "scale(1.15)";
};
rightIcon.onmouseout = () => {
pathElement.setAttribute("fill", "#5D5D5D");
rightIcon.style.transform = "scale(1)";
};
// 将`path`元素添加到`svg`元素中
rightIcon.appendChild(pathElement);
// 将`svg`元素添加到`rightPart`
rightPart.appendChild(rightIcon);
}
if (action2 == null) {
rightPart.disabled = true;
} else {
rightPart.onclick = () => {
console.log("103 执行右键菜单动作植入: " + label);
action2();
hideContextMenu(); // 执行动作后隐藏右键菜单
console.log('103 执行右键菜单动作后隐藏右键菜单');
//contextMenu.style.display = 'none';
};
}
menuItem.appendChild(rightPart);
return menuItem;
}
// 104 隐藏右键菜单
function hideContextMenu() {
if (contextMenu.style.display === 'none') {
return;
}
contextMenu.style.display = 'none';
document.removeEventListener('keydown', handleShortcut); // 移除快捷键事件
}
// 105 显示右键菜单
function showContextMenu(event) {
// 先把菜单显示在屏幕外,以便能获取其尺寸
contextMenu.style.left = '-9999px';
contextMenu.style.top = '-9999px';
contextMenu.style.display = 'block';
// 更新右键菜单顶部的标题以显示当前设置组的名称
menuTitle.innerHTML = setting_texts[setting_current_index].split('\n')[0];
console.log('105 当前设置组: ', menuTitle.innerHTML);
console.log("105 显示右键菜单:actions 数组: ", actions);
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const menuWidth = contextMenu.offsetWidth;
const menuHeight = contextMenu.offsetHeight;
let x = event.clientX;
let y = event.clientY;
// 确保菜单不会超出屏幕右侧
if (x + menuWidth > windowWidth) {
x = windowWidth - menuWidth;
}
// 确保菜单不会超出屏幕底部
if (y + menuHeight > windowHeight) {
y = windowHeight - menuHeight;
}
contextMenu.style.left = `${x}px`;
contextMenu.style.top = `${y}px`;
document.addEventListener('keydown', handleShortcut);
event.stopPropagation();
}
// 106 创建右键菜单
function handleShortcut(event) {
console.log("106 处理快捷键: " + event.key);
console.log("106 当前动作数组: ", actions); // 添加这行来调试
let key = parseInt(event.key, 10);
if (key > 0 && key <= actions.length) {
console.log("106 执行快捷键对应的动作: " + key);
document.removeEventListener('keydown', handleShortcut); // 先移除快捷键事件
actions[key - 1](); // 然后执行动作
hideContextMenu(); // 最后隐藏右键菜单
}
}
////////////////////////// Easy Click functions //////////////////////////
// 200 初始化
let isUpdating = false;
let rerunTimeout;
let intervalID;
let shouldContinue = true;
// 201 创建点击事件处理器
async function clickHandler(event) {
//event.preventDefault();
console.log('201 执行 clickHandler'); // 执行点击事件处理器
const inputElement = document.getElementById('prompt-textarea'); // 获取输入框
console.log('201 获取输入框元素');
inputElement.value = this.getAttribute("data-text"); // 将链接文本添加到输入框
console.log('201 设置输入框值');
const inputEvent = new Event('input', { 'bubbles': true }); // 创建input事件
console.log('201 创建 input 事件');
inputElement.dispatchEvent(inputEvent); // 触发input事件
console.log('201 触发 input 事件');
await new Promise(resolve => setTimeout(resolve, 50));
console.log('201 50ms 延时完成');
const sendButton = document.querySelector('[data-testid="send-button"]');
console.log('201 获取发送按钮');
if (sendButton) {
console.log('201 点击发送按钮');
sendButton.click();
console.log('201 开启监听');
// 滚动到底部
simulateClick() // 底部。
}
}
// 202 创建点击事件处理器
function replace_text(original) {
clicks.forEach(([regExpression, template]) => {
original = original.replace(regExpression, (match, p1) => {
// 如果匹配项已经是链接的一部分,则直接返回匹配项
if (original.lastIndexOf('<a', match.index) < original.lastIndexOf('</a>', match.index)) {
return match;
}
let replaced = template.replace('{__PLACE_HOLDER__}', p1);
return `<a class="custom-link" data-text="${replaced}">${'[[' + p1 + ']]'}</a>`;
});
});
return original;
}
// 203 创建点击事件处理器
function processElement(element) {
if (isUpdating) {
return;
}
let innerHTML = element.innerHTML;
// 处理[[ ]] 符号
const bracketRegex = /\[\[(.*?)\]\]/g;
innerHTML = replace_text(innerHTML);
// 替换了 innerHTML 后,原本网页中 Copy code 之类的事件监听就失效了。
// 为了尽可能让两个功能共存,这里仅对文字有改动的重新赋值。
if (innerHTML != element.innerHTML) {
element.innerHTML = innerHTML;
}
// 给新生成的链接添加事件监听
const customLinks = element.querySelectorAll('.custom-link'); // 获取所有链接
customLinks.forEach(link => { // 给每个链接添加点击事件
link.addEventListener('click', clickHandler);// 添加点击事件处理器
});
}
// 新生成的链接,鼠标悬停时的效果是显示一个 tooltip,内容是链接的 data-text 属性的值。
// 204 这个部分是用来检测GPT是否在更新的
function checkUpdateStatus() {
if (!shouldContinue) return;
console.log('204 运行 checkUpdateStatus');
const allButtons = document.querySelectorAll('button');
const stopGeneratingElement = Array.from(allButtons).find(el => el.textContent.includes("Stop generating"));
if (!stopGeneratingElement && isUpdating) { // 内容更新完成
console.log('204 内容更新完成,准备添加链接');
isUpdating = false; // 更新状态设置为false
if (rerunTimeout) {
clearTimeout(rerunTimeout); // 清除延时
console.log('204 清除之前的延时');
}
// 设置延时,等待2秒
rerunTimeout = setTimeout(() => {
console.log('204 延时结束,开始添加链接'); // 延时结束,开始添加链接
// 开始转化链接
Convertlink()
// // 先找到父级对象
// const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
// parentElements.forEach(parent => {
// // 在父级对象下面找特定的子元素
// const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
// chatRecordElements.forEach(processElement);
// });
stopListening();
}, 300);
}
}
// 205 创建点击事件处理器, 开始监听
function startListening() {
isUpdating = true;
shouldContinue = true;
// intervalID = setInterval(checkUpdateStatus, 1000);
Convertlink()
console.log('205 监听已开启');
}
// 206 创建点击事件处理器, 停止监听
function stopListening() {
shouldContinue = false;
clearInterval(intervalID);
console.log('206 监听已停止');
updatepageMenuItems();
}
////////////////////////// Settings functions //////////////////////////
let isModalOpen = false; // 用于判断模态框是否已经打开
// 300 显示设置模态框
function showSettingsModal() {
console.log("300 显示设置模态框");
isModalOpen = true;
console.log("300 ismoalopen: " + isModalOpen);
// 301 创建模态框
const modal = document.createElement('div');
modal.className = 'settings-modal';
// 302 创建模态框内容
const modalContent = document.createElement('div');
modalContent.className = 'settings-content';
// 303 创建文本区
const textarea = document.createElement('textarea');
textarea.className = 'settings-textarea';
textarea.value = current_setting_text;
// 304 创建提交按钮
const submitButton = document.createElement('button');
submitButton.className = 'settings-submit';
submitButton.textContent = '应用设置';
// 305 创建下拉菜单
const settingsDropdown = document.createElement('select');
settingsDropdown.className = 'settings-dropdown';
setting_texts.forEach((text, index) => {
const option = document.createElement('option');
option.value = index;
option.text = text.split('\n')[0]; // Assuming the first line is a title or identifier
settingsDropdown.appendChild(option);
});
// 306 设置默认选中的设置
settingsDropdown.selectedIndex = setting_current_index;
settingsDropdown.addEventListener('change', (e) => {
const selectedIndex = e.target.value;
textarea.value = setting_texts[selectedIndex];
if (setting_texts.length <= 1) {
deleteSettingButton.disabled = true;
} else {
deleteSettingButton.disabled = false;
}
});
// 307 创建添加新设置按钮
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.gap = '10px'; // 两个按钮之间的间距
const newSettingButton = document.createElement('button');
newSettingButton.textContent = '添加新功能组';
newSettingButton.className = 'settings-submit';
newSettingButton.addEventListener('click', () => {
textarea.value = setting_new_setting_text; // 设置文本区的值为新设置的默认值
setting_texts.push(textarea.value); // 将新设置添加到 setting_texts 数组中
const option = document.createElement('option');
option.value = setting_texts.length - 1;
option.text = setting_new_setting_text.split('\n')[0];
settingsDropdown.appendChild(option);
settingsDropdown.value = setting_texts.length - 1;
deleteSettingButton.disabled = false;
});
// 308 创建删除设置按钮
const deleteSettingButton = document.createElement('button');
deleteSettingButton.textContent = '删除当前功能组';
deleteSettingButton.className = 'settings-submit';
// 309 设置删除按钮的点击事件
deleteSettingButton.addEventListener('click', () => {
// 如果只剩一个设置,则不进行删除操作
if (setting_texts.length <= 1) {
return;
}
let toDelete = settingsDropdown.selectedIndex;
// 从 setting_texts 数组中删除设置
setting_texts.splice(toDelete, 1);
// 从 settingsDropdown 中删除对应的选项
settingsDropdown.remove(toDelete);
// 如果删除的是第0项或列表中的最后一项,则默认选择第0项
if (toDelete === 0 || toDelete === setting_texts.length) {
settingsDropdown.selectedIndex = 0;
setting_current_index = 0;
} else {
// 否则选择之前的项
settingsDropdown.selectedIndex = toDelete - 1;
setting_current_index = toDelete - 1;
}
// 更新文本区的值为当前选中的设置
textarea.value = setting_texts[setting_current_index];
// 保存到 localStorage
localStorage.setItem(LSID_SETTING_TEXTS, JSON.stringify(setting_texts));
localStorage.setItem(LSID_SETTING_CURRENT_INDEX, setting_current_index);
deleteSettingButton.disabled = setting_texts.length <= 1;
});
// 310 设置删除按钮的状态,检查是否只剩一个设置,如果是,则禁用删除按钮
if (setting_texts.length <= 1) {
deleteSettingButton.disabled = true;
}
// 311 将所有元素添加到模态框中
buttonsContainer.appendChild(newSettingButton);
buttonsContainer.appendChild(deleteSettingButton);
modalContent.appendChild(settingsDropdown);
modalContent.appendChild(buttonsContainer);
modalContent.appendChild(textarea);
modalContent.appendChild(submitButton);
modal.appendChild(modalContent);
document.body.appendChild(modal);
// // 312 点击模态框外部时关闭模态框
// modal.addEventListener('click', (e) => {
// if (e.target === modal) {
// modal.remove();
// console.log("关闭模态框-点击外部");
// isModalOpen = false;
// console.log("ismoalopen: " + isModalOpen);
// }
// });
// 313 点击提交按钮时保存设置
submitButton.addEventListener('click', () => {
console.log("313 点击提交按钮 保存设置");
actions = []; // 每次保存的时候清空动作数组,以便重新生成。
const selectedSettingIndex = settingsDropdown.selectedIndex; // 获取当前选中的设置的索引
if (typeof setting_texts[selectedSettingIndex] === 'undefined') {
console.error("Trying to save a setting that doesn't exist.");
return;
}
// 保存到 localStorage
setting_texts[selectedSettingIndex] = textarea.value;
localStorage.setItem(LSID_SETTING_TEXTS, JSON.stringify(setting_texts));
localStorage.setItem(LSID_SETTING_CURRENT_INDEX, selectedSettingIndex.toString());
current_setting_text = textarea.value;
setting_current_index = selectedSettingIndex;
if (current_setting_text) {
updateMenuItems();
updatepageMenuItems();
}
modal.remove();
isModalOpen = false;
console.log("313 ismoalopen: " + isModalOpen);
});
// 314 按下 ESC 键时关闭模态框
document.addEventListener('keydown', handleEscCloseModal);
}
// 315 按下 ESC 键时关闭模态框
function handleEscCloseModal(event) {
if (event.key === 'Escape') {
const modal = document.querySelector('.settings-modal');
if (modal) {
modal.remove();
console.log("315 关闭模态框-按下 ESC 键");
isModalOpen = false;
}
// 移除键盘事件监听器
document.removeEventListener('keydown', handleEscCloseModal);
}
}
// 316 将所选更新到模版。
function addTemplate(selectedText) {
console.log("316 将所选更新到模版。");
let original = setting_texts[setting_current_index];
let toAdd = '\n🪄🪄🪄🪄🪄🪄🪄🪄\n' + '新模版名称'+ '\n' + selectedText + '\n' + '用户输入:{__PLACE_HOLDER__}\n';
// 找到 original 中第一个 📖📖📖📖📖📖📖📖 的位置,在此之前插入 textarea.value
// 如果没有找到,则在末尾插入
let index = original.indexOf('📖📖📖📖📖📖📖📖');
if (index >= 0) {
setting_texts[setting_current_index] = original.slice(0, index) + toAdd + original.slice(index);
} else {
setting_texts[setting_current_index] = original + toAdd;
}
localStorage.setItem(LSID_SETTING_TEXTS, JSON.stringify(setting_texts));
current_setting_text = setting_texts[setting_current_index];
if (current_setting_text) {
updateMenuItems();
updatepageMenuItems();
}
}
////////////////////////// Menu functions //////////////////////////
// 400 解析菜单初始化
let menus = []; // 用于存储菜单项的数组
let clicks = [] // 用于存储点击事件的数组
// 401 从设置文本中解析菜单
function parseMenus(settingsText) { // 从设置文本中解析菜单
const buttonData = settingsText.split("🪄🪄🪄🪄🪄🪄🪄🪄").slice(1);
buttonData.forEach(data => { // 遍历每个菜单项
const lines = data.trim().split("\n"); // 按行分割菜单项
if (lines.length >= 2) { // 如果菜单项的行数大于等于2
const name = lines[0]; // 第一行是菜单项的名称
const content = lines.slice(1).join("\n"); // 后续行组成菜单项的内容
menus.push([name, content]); // 将菜单项的名称和内容添加到菜单项数组中
// console.log("401 添加菜单项: " + name);
}
});
}
// 402 从设置文本中解析点击事件
function parseClicks(settingText) {
// 根据 📖📖📖📖📖📖📖📖 分割设置文件,并移除首尾的空值
const configArray = settingText.split('📖📖📖📖📖📖📖📖').filter(Boolean);
// 遍历每个设置
configArray.forEach(config => {
// 按行分割配置
const lines = config.trim().split('\n');
// 第一行是正则表达式
const regExpression = new RegExp(lines[0], 'g');
// 后续行组成替换模板
const template = lines.slice(1).join('\n');
// 将正则表达式和模板添加到正则数组中
clicks.push([regExpression, template]);
});
}
// 403 解析设置文本
function parseSettingsText(settingsText) {
menus.length = 0; // Clear the existing array
clicks.length = 0; // Clear the existing array
let splitted = settingsText.split("📖📖📖📖📖📖📖📖")
if (splitted.length < 2) {
parseMenus(settingsText); // 解析菜单
} else {
parseMenus(splitted[0]); // 解析菜单
parseClicks(splitted.slice(1).join("📖📖📖📖📖📖📖📖"));
}
}
// 404 更新菜单项
function updateMenuItems() {
console.log("404 开始更新菜单项");
//每次更新菜单项时,先清空菜单项数组
menus = [];
parseSettingsText(current_setting_text); // 解析设置文本
menuContainer.innerHTML = '';
// 重新添加 menuTitle
menuTitle.innerHTML = setting_texts[setting_current_index].split('\n')[0];
menuContainer.appendChild(menuTitle);
menus.forEach((menu, index) => { // 注意这里添加了 index
menuContainer.appendChild(
createMenuItem(
index + 1, // 添加序号,注意这里 index + 1
menu[0],
iconSend,
iconEdit,
async function() {
await sendToGPT(menu[1], window.getSelection().toString().trim(), true);
},
async function() {
await sendToGPT(menu[1], window.getSelection().toString().trim(), false);
}
)
);
});
// 添加模版
menuContainer.appendChild(createMenuItem(menus.length + 1, '添加模版', iconSend, null, function() {addTemplate(window.getSelection().toString().trim());}, null));
// 设置按钮的快捷键可以设为 menus.length + 1
menuContainer.appendChild(createMenuItem(menus.length + 2, '设置', iconSetting, null, function() {showSettingsModal();}, null));
console.log("404 更新菜单项完成");
}
////////////////////////// Main //////////////////////////
// 500 初始化
// 501 创建样式元素
updateMenuItems();
// 502 激活右键菜单
document.addEventListener('mouseup', function(event) {
if (isModalOpen) {
return; // 如果模态框打开,则不执行后面的代码
}
const selectedText = window.getSelection().toString(); // 获取选中的文本
// 检查 Command 键(Mac)或 Ctrl 键(Windows)是否被按下
// 如果没有选中文本或没有按下 Command 键(Mac)或 Ctrl 键(Windows)
if (selectedText.length == 0 || !event.metaKey) {
hideContextMenu();
} else {
showContextMenu(event);
console.log("502 显示右键菜单");
}
});
// 503 监听鼠标事件,双击时显示右键菜单
// document.addEventListener('dblclick', function(event) {
// showContextMenu(event);
// console.log("503 双击显示右键菜单");
// });
// 504 监听鼠标事件,当鼠标移动出右键菜单区域时,隐藏菜单,菜单隐藏之后,停止监听。
// document.addEventListener('mousemove', function(event) {
// if (isModalOpen) {
// return; // 如果模态框打开,则不执行后面的代码
// }
// if (contextMenu.style.display === 'none') {
// return;
// }
// const mouseX = event.clientX;
// const mouseY = event.clientY;
// const menuX = contextMenu.offsetLeft;
// const menuY = contextMenu.offsetTop;
// const menuWidth = contextMenu.offsetWidth;
// const menuHeight = contextMenu.offsetHeight;
// if (mouseX < menuX || mouseX > menuX + menuWidth || mouseY < menuY || mouseY > menuY + menuHeight) {
// hideContextMenu();
// console.log("504 鼠标移动出右键菜单区域,隐藏菜单");
// }
// });
////////////////////////// 快捷键配置 //////////////////////////
// 600 快捷键配置
let isGPressed = false; // 用于跟踪 'g' 键是否被按下
let timer;
let gPressCount = 0; // 用于跟踪 'g' 键被按下的次数
let lastGPressTime = 0; // 用于跟踪上一次 'g' 键被按下的时间
// 601 模拟官方toast
function showToast(message) {
// 获取父容器元素
const parentContainer = document.querySelector('.pointer-events-none');
// 创建新的div元素
const newDiv = document.createElement('div');
newDiv.setAttribute('data-state', 'entered');
newDiv.setAttribute('class', 'toast-root');
newDiv.setAttribute('style', 'height: 50px; margin-bottom: 0px;');
// 创建toast内容
const toastContent = `
<div class="w-full p-1 text-center md:w-auto md:text-justify">
<div class="px-3 py-2 rounded-md text-white inline-flex flex-row border pointer-events-auto gap-2 border-green-500 bg-green-500" role="alert">
<div class="mt-1 flex-shrink-0 flex-grow-0">
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<div class="flex-1 justify-center gap-2">
<div class="whitespace-pre-wrap text-left">${message}</div>
</div>
</div>
</div>
`;
// 设置新的div的内容为toast内容
newDiv.innerHTML = toastContent;
// 将新的div添加到父容器中
parentContainer.appendChild(newDiv);
// 设置定时器,在1秒后自动移除新的div
setTimeout(() => {
parentContainer.removeChild(newDiv);
}, 2000);
}
// 602 监听键盘事件,当按下复合快捷键 cmd+d 键时显示设置模态框,两个按键之间的间隔不能超过300ms,否则会被认为是两次按键。指令不执行。
document.addEventListener('keydown', function(event) {
// 检查是否同时按下了Cmd键(Mac下的Ctrl键)和分号键
if (event.key === 'd' && event.metaKey) {
if (!isModalOpen) {
showSettingsModal();
}
event.preventDefault(); // 防止浏览器默认行为
}
});
// 603 监听键盘事件,当按下复合快捷键 “g”+ 数字键,切换不同的设置 setting_texts, 数字1代表第一个设置,数字2代表第二个设置,以此类推。
// 切换的时候需要更新菜单项和处理actions数组
document.addEventListener('keydown', function(event) {
// console.log("603 调试信息: 键盘事件触发,当前按键是", event.key);
if (isModalOpen) {
return; // 如果模态框打开,则不执行后面的代码
}
// 检查是否同时按下了g键+数字键
if (event.key === 'g') {
if (!isGPressed) {
isGPressed = true;
timer = setTimeout(() => {
isGPressed = false;
gPressCount = 0;
}, 300);
}
gPressCount++;
lastGPressTime = Date.now();
event.preventDefault(); // 防止浏览器默认行为
} else if (event.key >= '0' && event.key <= '9' && isGPressed) {
clearTimeout(timer);
isGPressed = false;
gPressCount = 0;
const settingIndex = parseInt(event.key, 10) - 1;
if (settingIndex < setting_texts.length) {
setting_current_index = settingIndex;
current_setting_text = setting_texts[settingIndex];
updateMenuItems();
updatepageMenuItems();
showToast('切换到设置: ' + setting_texts[settingIndex].split('\n')[0]);
}
event.preventDefault(); // 防止浏览器默认行为
} else if (event.key === 'g' && isGPressed && Date.now() - lastGPressTime > 300) {
clearTimeout(timer);
isGPressed = false;
gPressCount = 0;
event.preventDefault(); // 防止浏览器默认行为
}
});
// 604 选中文本后的数字快捷键。如果当前选中了文本,按数字1-9键,执行对应的菜单项,菜单来自于上下文菜单,没有选中文本的时候按数字键不执行任何操作。
document.addEventListener('keydown', function(event) {
// console.log("604 调试信息: 键盘事件触发,当前按键是", event.key);
if (isModalOpen) {
return; // 如果模态框打开,则不执行后面的代码
}
// 检查是否选中了文本
const selectedText = window.getSelection().toString();
if (selectedText.length == 0) {
return;
}
// 检查是否按下了数字键
let key = parseInt(event.key, 10);
if (key > 0 && key <= actions.length) {
console.log('604 当前设置组: ', menuTitle.innerHTML); // 输出当前设置组的名称
// 输出当前执行的动作名称
if (menus && menus[key - 1] && menus[key - 1][0]) {
console.log('604 执行快捷键对应的动作: ' + key + '. ' + menus[key - 1][0]);
} else {
console.log('604 调试信息: menus 数组或者其元素未定义');
}
actions[key - 1](); // 执行对应的动作
showToast('执行动作: ' + menus[key - 1][0]);
simulateClick() // 底部。
}
});
////////////////////////// 悬浮菜单 //////////////////////////
// 700 页面内悬浮菜单
const pageMenu = document.createElement('div');
const pageMenuContainer = document.createElement('div');
pageMenu.id = 'pageMenu'; // 给这个 div 元素设置 id
pageMenuContainer.id = 'pageMenuContainer'; // 给这个 div 元素设置 id
const pageMenuTitle = document.createElement('div');
pageMenuContainer.appendChild(pageMenuTitle); // 把菜单标题添加到菜单容器中
pageMenu.appendChild(pageMenuContainer); // 把菜单容器添加到右键菜单中
pageMenu.classList.add('page-menu'); // 添加CSS类
pageMenuTitle.classList.add('pageMenuTitle'); // 添加CSS类
// 找到父容器
// const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
// parentContainer.appendChild(pageMenu);
document.body.appendChild(pageMenu);
// 701 更新pageMenu菜单项
function updatepageMenuItems() {
// 找到父容器
// const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
// parentContainer.appendChild(pageMenu);
document.body.appendChild(pageMenu);
console.log("701 开始更新菜单项");
//每次更新菜单项时,先清空菜单项数组
menus = [];
parseSettingsText(current_setting_text); // 解析设置文本
pageMenuContainer.innerHTML = '';
// 重新添加 menuTitle
pageMenuTitle.innerHTML = setting_texts[setting_current_index].split('\n')[0]; // 更新右键菜单顶部的标题以显示当前设置组的名称
pageMenuContainer.appendChild(pageMenuTitle);
menus.forEach((menu, index) => { // 遍历菜单项数组
pageMenuContainer.appendChild( //new
createMenuItem(
index + 1, // 添加序号,注意这里 index + 1
menu[0],
iconSend,
iconEdit,
async function() {
await sendToGPT(menu[1], window.getSelection().toString().trim(), true);
},
async function() {
await sendToGPT(menu[1], window.getSelection().toString().trim(), false);
}
)
);
});
// 添加设置菜单项
pageMenuContainer.appendChild(createMenuItem(menus.length + 1, '加入模版',iconSetting, null, function() {addTemplate(window.getSelection().toString().trim());}, null));
pageMenuContainer.appendChild(createMenuItem(menus.length + 2, '设置', iconSetting, null, function() {showSettingsModal();}, null));
console.log("701 更新菜单项完成");
}
// 702 生成的页面悬浮菜单可以点击拖动。
// 初始化变量
let isDragging = false; // 用于判断是否正在拖动
let isMouseDown = false; // 用于判断鼠标是否按下
let mouseDownTimer = null; // 用于识别长按的计时器
let shouldSwitchGroup = false; // 新增变量,用于判断是否切换到下一个设置
// 获取页面元素
const pageMenuTitleElement = document.querySelector('.pageMenuTitle');
const pageMenuElement = document.querySelector('#pageMenu');
// 鼠标按下事件
pageMenuTitleElement.addEventListener('mousedown', function(event) {
isMouseDown = true;
shouldSwitchGroup = true;
console.log("调试信息: 鼠标按下,设置 isMouseDown = true");
// 设置延时计时器
mouseDownTimer = setTimeout(() => {
if (isMouseDown) {
isDragging = true;
shouldSwitchGroup = false;
console.log("调试信息: 长按识别,进入拖动模式");
// 计算鼠标与菜单的偏移
const offsetX = event.clientX - pageMenuElement.offsetLeft;
const offsetY = event.clientY - pageMenuElement.offsetTop;
// 鼠标移动事件
document.addEventListener('mousemove', function(event) {
if (isDragging) {
// 更新菜单的位置
pageMenuElement.style.left = `${event.clientX - offsetX}px`;
pageMenuElement.style.top = `${event.clientY - offsetY}px`;
}
});
}
}, 150);
});
// 鼠标抬起事件
document.addEventListener('mouseup', function() {
if (mouseDownTimer) {
clearTimeout(mouseDownTimer); // 清除计时器
}
isMouseDown = false;
isDragging = false;
});
// 703 鼠标点击分组标题切换到设置,点击标题栏左半边部分,切换到上一组设置,点击标题栏右半边部分,切换到下一组设置。
pageMenuTitleElement.addEventListener('click', function(event) {
if (shouldSwitchGroup) {
if (event.offsetX < pageMenuTitleElement.offsetWidth / 2) {
// 切换到上一组设置
setting_current_index--;
if (setting_current_index < 0) {
setting_current_index = setting_texts.length - 1;
}
current_setting_text = setting_texts[setting_current_index];
updatepageMenuItems();
updateMenuItems();
showToast('前一组设置: ' + setting_texts[setting_current_index].split('\n')[0]);
} else {
// 切换到下一组设置
setting_current_index++;
if (setting_current_index >= setting_texts.length) {
setting_current_index = 0;
}
current_setting_text = setting_texts[setting_current_index];
updatepageMenuItems();
updateMenuItems();
showToast('后一组设置: ' + setting_texts[setting_current_index].split('\n')[0]);
}
}
shouldSwitchGroup = true;
});
// 704 Ctrl+C 快捷键启用 checkUpdateStatus()
document.addEventListener('keydown', function(event) {
if (event.key === 'c' && event.ctrlKey) {
console.log("702 Ctrl+C 触发");
Convertlink()
}
});
// 定义一个函数,触发之后效果等效于 704 Ctrl+C 快捷键 之后效果。函数名称是转化链接的意思
function Convertlink() {
console.log("Convertlink 转换链接");
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
parentElements.forEach(parent => {
// 在父级对象下面找特定的子元素
const chatRecordElements = parent.querySelectorAll('div.markdown.prose.w-full.break-words,li');
// 定义一个函数,处理每个元素
chatRecordElements.forEach(processElement);
}
);
}
// 生成第二个独立菜单,这个菜单展示的所有设置组名称,点击不同的名称可以切换到对应的设置组,并同步更新其他菜单
// 比如,当前有5个设置组,那这个菜单就展示5个设置组的名称,点击第一个名称,切换到第一个设置组,点击第二个名称,切换到第二个设置组,以此类推。
// 菜单也有一个标题,表示用“分组面板”
// 705 创建分组面板
const groupMenu = document.createElement('div');
const groupMenuContainer = document.createElement('div');
groupMenu.id = 'groupMenu'; // 给这个 div 元素设置 id
groupMenu.classList.add('group-menu'); // 添加CSS类
groupMenu.id = 'groupMenu'; // 给这个 div 元素设置 id
groupMenuContainer.classList.add('groupMenuContainer'); // 添加CSS类
const groupMenuTitle = document.createElement('div');
groupMenuTitle.textContent = '分组面板'; // 设置标题
groupMenuTitle.classList.add('groupMenuTitle'); // 添加CSS类
groupMenuContainer.appendChild(groupMenuTitle); // 把菜单标题添加到菜单容器中
groupMenu.appendChild(groupMenuContainer); // 把菜单容器添加到右键菜单中
groupMenu.classList.add('group-menu'); // 添加CSS类
// 找到父容器
const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
parentContainer.appendChild(groupMenu);
// 706 更新分组面板菜单项
function updateGroupMenuItems() {
console.log("706 开始更新菜单项");
//每次更新菜单项时,先清空菜单项数组
menus = [];
parseSettingsText(current_setting_text); // 解析设置文本
groupMenuContainer.innerHTML = '';
// 重新添加 menuTitle ,菜单名不需要更新。一直是 “分组面板”
groupMenuTitle.textContent = '分组面板'
groupMenuContainer.appendChild(groupMenuTitle);
// 添加菜单项,仅保留分组名称,不需要图标
setting_texts.forEach((setting, index) => { // 遍历菜单项数组
const menuItem = createMenuItem(
index + 1, // 添加序号,注意这里 index + 1
setting.split('\n')[0],
null,
null,
async function() {
setting_current_index = index;
current_setting_text = setting_texts[setting_current_index];
updatepageMenuItems();
updateMenuItems();
showToast('切换到设置: ' + setting_texts[setting_current_index].split('\n')[0]);
// 移除其他菜单项的高亮
const allMenuItems = document.querySelectorAll('.menuItem'); // 假设你的菜单项有一个共同的 'menuItem' 类
allMenuItems.forEach((item) => {
item.classList.remove('highlight');
});
// 添加高亮到当前被点击的菜单项
menuItem.classList.add('highlight');
},
null
);
menuItem.classList.add('menuItem'); // 添加一个 'menuItem' 类到每一个菜单项,以便稍后选择它们
groupMenuContainer.appendChild(menuItem);
});
console.log("706 更新菜单项完成");
};
updateGroupMenuItems()
////////////////////////// prompt搜索功能 //////////////////////////
// 我需要做一个搜索功能,搜索的对象右键菜单的菜单项。
// 界面中有一个输入框,用户可以在这个输入框中输入文字,会模糊匹配右键菜单中的菜单项,如果匹配到了,则选中这个菜单项,如果没有匹配到,则不做任何操作。
// 当用户按下回车键的时候,会执行对应菜单的指令。
// 以下是代码:
// 800 创建搜索框和下拉列表的容器
const searchContainer = document.createElement('div');
searchContainer.id = 'searchContainer';
searchContainer.classList.add('searchContainer');
document.body.appendChild(searchContainer);
// 800 在搜索框上方添加一个提示文字,文字展示内容为 savedSelectedText。
const searchBoxTip = document.createElement('div');
searchBoxTip.id = 'searchBoxTip';
searchBoxTip.classList.add('searchBoxTip');
searchBoxTip.textContent = ''
searchContainer.appendChild(searchBoxTip);
// 800 创建搜索框
const searchBox = document.createElement('input');
searchBox.id = 'searchBox';
searchBox.classList.add('searchBox');
searchBox.setAttribute('autocomplete', 'off');
searchContainer.appendChild(searchBox);
// 801 创建下拉列表
const searchBoxList = document.createElement('div');
searchBoxList.id = 'searchBoxList';
searchBoxList.classList.add('searchBoxList');
searchContainer.appendChild(searchBoxList);
let selectedMenuItemIndex = -1; // 选定的菜单项索引
// 802 搜索框事件处理
searchBox.addEventListener('input', function (event) {
console.log("802 搜索框事件处理");
// const searchValue = event.target.value.trim();
const searchValue = event.target.value.trim().toLowerCase(); // 将输入转为小写
// 在这里重置 selectedMenuItemIndex
selectedMenuItemIndex = -1;
const matchedMenus = menus.filter(menu =>
menu[0].toLowerCase().includes(searchValue) // 将菜单项转为小写进行匹配
);
// 清空下拉列表
searchBoxList.innerHTML = '';
if (searchValue.length > 0 && matchedMenus.length > 0) {
// 有匹配的菜单项
matchedMenus.forEach(menu => {
const listItem = document.createElement('div');
listItem.classList.add('searchBoxListItem');
listItem.textContent = menu[0];
// 如果是第一个匹配的菜单项,设置为选中状态
if (matchedMenus.indexOf(menu) === 0) {
selectedMenuItemIndex = 0;
listItem.classList.add('selected');
}
// 802.1 下拉列表项的点击事件
listItem.addEventListener('click', function () {
// sendToGPT(menu[1], window.getSelection().toString().trim(), true); // 执行对应的指令
sendToGPT(menu[1], savedSelectedText, true); // 使用保存的文本作为参数
simulateClick(); // 跳转到页面底部
console.log('savedSelectedText: ', savedSelectedText);
searchBoxList.style.display = 'none'; // 隐藏下拉菜单
console.log('执行指令:', menu[0]);
savedSelectedText = ''; // 清除保存的文本
// 如果是第一个匹配的菜单项,设置为选中状态
if (matchedMenus.indexOf(menu) === 0) {
selectedMenuItemIndex = 0;
listItem.classList.add('selected');
}
// 在这里重置 selectedMenuItemIndex
selectedMenuItemIndex = -1;
searchContainer.style.display = 'none';
});
listItem.addEventListener('mouseover', function () {
// 鼠标悬停效果
listItem.classList.add('hovered');
});
listItem.addEventListener('mouseout', function () {
// 移除悬停效果
listItem.classList.remove('hovered');
});
searchBoxList.appendChild(listItem);
});
// 显示下拉列表
searchBoxList.style.display = 'block';
} else {
// 没有匹配的菜单项
searchBoxList.style.display = 'none';
}
});
// 803 键盘事件处理
searchBox.addEventListener('keydown', function (event) {
console.log("803 键盘事件处理");
if (event.key === 'ArrowUp') {
// 上箭头键
event.preventDefault();
selectPreviousMenuItem();
} else if (event.key === 'ArrowDown') {
// 下箭头键
event.preventDefault();
selectNextMenuItem();
} else if (event.key === 'Enter') {
// 回车键
event.preventDefault();
executeSelectedMenuItem(); // 807
}
});
// 804 选定上一项
function selectPreviousMenuItem() {
if (selectedMenuItemIndex > 0) {
selectMenuItem(selectedMenuItemIndex - 1);
}
}
// 805 选定下一项
function selectNextMenuItem() {
const menuItems = searchBoxList.querySelectorAll('.searchBoxListItem');
if (selectedMenuItemIndex < menuItems.length - 1) {
selectMenuItem(selectedMenuItemIndex + 1);
}
}
// 806 选定菜单项
function selectMenuItem(index) {
console.log(`806 选择菜单项:${index}`);
const menuItems = searchBoxList.querySelectorAll('.searchBoxListItem');
// 如果之前有选中的菜单项,则取消它的高亮显示
if (selectedMenuItemIndex !== -1 && menuItems[selectedMenuItemIndex]) {
menuItems[selectedMenuItemIndex].classList.remove('selected');
}
// 高亮显示新选中的菜单项
menuItems[index].classList.add('selected');
selectedMenuItemIndex = index;
}
// 807 执行选定的菜单项,相当于直接发送的效果
function executeSelectedMenuItem() {
console.log(`807 开始`);
const menuItems = searchBoxList.querySelectorAll('.searchBoxListItem'); // 获取所有的菜单项
if (menuItems.length > 0) {
// 如果selectedMenuItemIndex为-1,默认选择第一个菜单项
const itemToExecute = menuItems[selectedMenuItemIndex !== -1 ? selectedMenuItemIndex : 0];
const menuText = itemToExecute.textContent.trim();
// 根据菜单文本查找对应的菜单指令
const matchedMenu = menus.find(menu => menu[0] === menuText);
if (matchedMenu) {
// 等同于点击了下拉菜单的匹配的菜单项的指令
sendToGPT(matchedMenu[1], savedSelectedText, true); // 使用保存的文本作为参数
console.log('807 savedSelectedText: ', savedSelectedText);
simulateClick(); // 条状到页面底部
}
searchBoxList.style.display = 'none'; // 隐藏下拉菜单
// 隐藏搜索框
searchContainer.style.display = 'none';
// 在这里重置 selectedMenuItemIndex
selectedMenuItemIndex = -1;
savedSelectedText = ''; // 清除保存的文本
}
}
// 809 点击页面其他地方,隐藏下拉列表
document.addEventListener('click', function(event) {
if (!searchBox.contains(event.target) && !searchBoxList.contains(event.target)) {
searchBoxList.style.display = 'none';
if (!searchBox.contains(event.target) && !searchBoxList.contains(event.target)) {
searchBoxList.style.display = 'none';
// 在这里重置 selectedMenuItemIndex
selectedMenuItemIndex = -1;
}
}
});
// 810 按下 ⌘ + G 键,显示搜索框
// 初始化变量
// 初始化变量,用于保存搜索框的上一次已知位置
let lastKnownPosition = { x: '50%', y: '50%' };
// 检查本地存储是否有保存的位置信息
const storedPosition = localStorage.getItem('searchBoxPosition');
if (storedPosition) {
lastKnownPosition = JSON.parse(storedPosition);
// 设置搜索框位置为上次保存的位置
searchContainer.style.left = lastKnownPosition.x;
searchContainer.style.top = lastKnownPosition.y;
}
// 810 按下 ⌘ + G 键,显示搜索框
document.addEventListener('keydown', function(event) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'settings-modal';
// 判断是否按下了⌘ + G (Cmd 键对应的 key 是 'Meta')
if (event.key === 'g' && event.metaKey) {
console.log("810 按下 ⌘ + G 键,显示搜索框");
const selection = window.getSelection();
savedSelectedText = '';
// 检查是否有选中的文本
if (selection && selection.toString().length > 0) {
savedSelectedText = selection.toString();
searchBoxTip.textContent = savedSelectedText;
}
if (selection.rangeCount > 0) {
savedSelection = selection.getRangeAt(0).cloneRange(); // 保存选中的文本的范围
const rect = savedSelection.getBoundingClientRect();
// 默认在屏幕正中,如果之前有保存的位置信息,则使用之前保存的位置
setPosition(searchContainer, lastKnownPosition.x, lastKnownPosition.y, 'fixed');
// 如果之前没有保存的位置信息,则将搜索框置于屏幕正中
if (!lastKnownPosition.x || !lastKnownPosition.y) {
searchContainer.style.left = '50%';
searchContainer.style.top = '50%';
searchContainer.style.transform = 'translate(-50%, -50%)'; // 调整搜索框使其正好位于中心位置
}
} else {
// 默认在屏幕正中,如果之前有保存的位置信息,则使用之前保存的位置
setPosition(searchContainer, lastKnownPosition.x, lastKnownPosition.y, 'fixed');
// 如果之前没有保存的位置信息,则将搜索框置于屏幕正中
if (!lastKnownPosition.x || !lastKnownPosition.y) {
searchContainer.style.left = '50%';
searchContainer.style.top = '50%';
searchContainer.style.transform = 'translate(-50%, -50%)'; // 调整搜索框使其正好位于中心位置
}
}
// 清空搜索框并使其可见
searchBox.value = '';
searchContainer.style.display = 'block';
searchBox.focus();
}
});
// 鼠标按下事件
searchContainer.addEventListener('mousedown', function(event) {
const boxRect = searchContainer.getBoundingClientRect();
const offsetX = event.clientX - boxRect.left;
const offsetY = event.clientY - boxRect.top;
// 当鼠标移动时更新搜索框的位置
document.onmousemove = function(moveEvent) {
const posX = moveEvent.clientX - offsetX;
const posY = moveEvent.clientY - offsetY;
setPosition(searchContainer, posX + 'px', posY + 'px');
// 保存搜索框的位置
lastKnownPosition = { x: posX + 'px', y: posY + 'px' };
localStorage.setItem('searchBoxPosition', JSON.stringify(lastKnownPosition));
};
// 当鼠标抬起时保存当前位置并取消mousemove事件
document.onmouseup = function() {
lastKnownPosition = {
x: searchContainer.getBoundingClientRect().left + 'px',
y: searchContainer.getBoundingClientRect().top + 'px'
};
document.onmousemove = document.onmouseup = null;
};
});
// 函数用于设置元素位置
function setPosition(element, x, y, positionType = '') {
element.style.left = x;
element.style.top = y;
if (positionType) {
element.style.position = positionType;
}
element.style.transform = '';
}
// 811 按下 ESC 键,隐藏搜索框
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
searchContainer.style.display = 'none';
}
});
// 812 确保当搜索框失去焦点时,恢复之前保存的选中状态
searchBox.addEventListener('blur', function() {
if (savedSelection) {
const newSelection = window.getSelection();
newSelection.removeAllRanges();
newSelection.addRange(savedSelection);
}
searchContainer.style.display = 'none';
});
////////////////////////// prompt自动化 //////////////////////////
// 900 定义AutoPrompt系统
const AutoPrompt = {
automenus: [],
executionState: 'waiting',
savedText: "",
// 901 解析设置文本并更新automenus数组
parseSettings: function() {
console.log("901 开始解析设置文本");
// ... 解析设置文本的代码 ...
this.automenus = [];
menus.forEach((menu) => {
this.automenus.push({
text: menu[0],
action: menu[1],
executed: false
});
});
console.log("901 获取当前设置组的所有菜单项完成");
},
// 902 手动触发第一个菜单项
triggerFirstMenu: function() {
// 重新解析设置
this.parseSettings();
// 重置 automenus 里的每个菜单项的 executed 状态为 false
this.automenus.forEach(menu => menu.executed = false);
// 重置其他参数到初始状态
this.savedText = "";
this.executionState = 'waiting';
if (this.observer) {
this.stopObserving();
}
if (this.automenus.length > 0) {
const firstMenu = this.automenus[0];
sendToGPT(firstMenu.action, '', true); // 设置 sendDirectly 参数为 true
firstMenu.executed = true;
// 延迟500毫秒后开始监听性能条目
setTimeout(() => {
this.executionState = 'executing';
this.observePerformance();
}, 1000);
} else {
console.log("没有找到任何菜单项");
}
},
// 903 查找和存储最新的内容
findLatestContent: function() {
console.log("903 查找最新生成的内容");
const parentElements = document.querySelectorAll('.flex.flex-grow.flex-col.gap-3.max-w-full');
if (parentElements.length) {
const lastParentElement = parentElements[parentElements.length - 1];
const chatRecordElements = lastParentElement.querySelectorAll('div.markdown.prose.w-full.break-words,li');
chatRecordElements.forEach(element => {
this.savedText += element.innerText + "\n";
});
if (this.executionState === 'waitingForFirstResponse') { // 如果当前状态是等待第一个响应
this.executionState = 'executing'; // 设置当前状态为执行中
this.executeNextMenu();
} else if (this.executionState === 'executing') {
this.executeNextMenu(); // 执行下一个菜单项
}
} else {
console.log("没有找到相关的父级对象");
}
},
// 904 自动执行下一个菜单项
executeNextMenu: function() {
console.log("904 自动执行下一个菜单项");
const nextMenu = this.automenus.find(menu => !menu.executed); // 找到第一个未执行的菜单项
if (nextMenu) { // 如果找到了未执行的菜单项
// 延迟1000毫秒后执行
setTimeout(() => {
this.executionState = 'executing'; // 设置当前状态为执行中
sendToGPT(nextMenu.action, this.savedText, true); // 设置 sendDirectly 参数为 true
nextMenu.executed = true;
}
, 1000);
} else {
console.log("所有任务已完成");
this.executionState = 'completed';
this.stopObserving();
}
},
observer: null,
// 906 监听性能条目
observePerformance: function() {
if (!this.observer) {
console.log("906 开始监听性能条目");
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('https://chat.openai.com/backend-api/conversation')) {
this.findLatestContent();
}
}
});
this.observer.observe({ entryTypes: ['resource'] });
}
},
// 907 停止监听性能条目
stopObserving: function() {
if (this.observer) {
console.log("907 停止监听性能条目");
this.observer.disconnect();
this.observer = null;
}
},
// 908 重置AutoPrompt系统
reset: function() {
console.log("908 重置AutoPrompt系统");
this.stopObserving();
this.automenus = [];
this.executionState = 'waiting';
this.savedText = "";
this.observePerformance();
},
// 909 初始化AutoPrompt系统
init: function() {
console.log("909 初始化AutoPrompt系统");
this.parseSettings();
this.observePerformance();
}
};
// 910 启动AutoPrompt系统
console.log("910 启动AutoPrompt系统");
AutoPrompt.init();
document.addEventListener("keydown", function(event) {
// 如果按下的是 ⌘ (Command) + .
if (event.metaKey && event.key === ".") {
AutoPrompt.triggerFirstMenu();
}
});
// 911 点击按钮滚动到页面底部
function simulateClick() {
// 使用querySelector方法定位元素
let btn = document.querySelector('button.cursor-pointer.rounded-full');
// 检查元素是否存在
if(btn) {
// 如果找到了元素,模拟点击它
btn.click();
console.log("元素被点击了!");
} else {
console.log("未找到指定的元素");
}
}
// 1000 监听性能条目
const observer = new PerformanceObserver((list) => { // 监听性能条目
for (const entry of list.getEntries()) { // 遍历性能条目
if (entry.name.includes('https://chat.openai.com/backend-api/conversation')) { // 如果是聊天记录的请求
// startListening(); // 开始监听
console.log("505 开始监听");
Convertlink() // 转化链接
updateMenuItems(); // 更新右键菜单
updatepageMenuItems(); // 更新页面内悬浮菜单
}
}
});
observer.observe({ entryTypes: ['resource'] }); // 监听资源性能条目
updatepageMenuItems()
////////////////////////// 更新日志 //////////////////////////
/* 更新日志:
更新了模块604,如果模态框打开,则不执行后面的代码 2021-10-04 23:21:25
更新了模块602,cmd+d 键显示设置模态框 2021-10-04 23:21:25
更新了模块601,模拟官方toast 2021-10-04 15:21:25
更新了模块603,修复了按下g+数字键,超出延迟时间后,gPressCount没有重置的问题 2023-10-05 01:28:42
更新了模块504,鼠标移动出右键菜单区域,菜单隐藏 2023-10-05 02:51:43
更新了所有模块的代码,让console.log输出 带编码的 调试信息 2023-10-05 02:49:16
取消了模块503,双击时显示右键菜单 2023-10-05 03:18:34
修改了悬浮菜单切换设置的逻辑,现在是点击标题栏左半边部分,切换到上一组设置,点击标题栏右半边部分,切换到下一组设置。2023-10-07 02:33:00
新增704,Ctrl+C 快捷键启用,实时转化链接。 2023-10-07 10:50:48
调整发送给GPT之后文本框的高度自适应 添加一个 发一条 bubble 消息 2023-10-07 11:24:54
添加了页面输入框的搜索功能,可以搜索右键菜单的菜单项。 2023-10-08 03:01:38
调整了页面输入框的搜索功能, 2023-10-08 23:04:10
添加了自动化prompt。2023-10-09
添加了分组菜单 2023-10-10
修改了个bug,对于每个匹配的文本,都会执行一次指令,现在改为只执行一次。 2023-10-11 17:18:34
添加功能,各种场景下的生成会直接跳转到底部。 2023-10-11 17:20:03
*/
目录插件
更新时间:2023-10-07 00:55:41
// ==UserScript==
// @name GPT History
// @namespace
// @version 0.1
// @description
// @author ttmouse & GPT-4
// @match https://chat.openai.com/*
// @grant none
// ==/UserScript==
// 我希望给GPT做一个目录。用js实现,在chrome中用console测试。
// 效果如下:
// 1.和GPT聊天的过程中,我发出去的内容会记录在在目录中,
// 2. 因为我发出去的内容可能会很长,在目录中进需要截取我发送内容的前10个字
// 3. 随着和GPT持续聊天,目录会自动更新
// 4. 目录支持点击跳转到对应的位置。
// 5. 目录有序号。
// 6. 打开已有聊天记录的页面,需要可以生成目录。
// 7. 用户有可能会修改历史聊天记录,目录需要重新生成。
////////////////////////// CSS //////////////////////////
const style = `
#directory {
position: absolute;
top: 81px;
left: 10px;
background-color: #7575751a;
color: #676767;
border-radius: 5px;
padding: 5px;
width: fit-content;
font-size: 15px;
display: inline-block;
}
#directoryTitle {
font-weight: 700;
}
.placeholder {
height: 70px; /* 你想要保留的顶部距离 */
visibility: hidden;
}
`;
const styleElement = document.createElement('style');
styleElement.innerHTML = style;
document.head.appendChild(styleElement);
////////////////////////// JS //////////////////////////
let isMouseDown = false;
let isDragging = false;
let mouseDownTimer;
// A01. 创建并设置目录容器
console.log("A01. 创建目录容器");
const directoryDiv = document.createElement('div');
directoryDiv.id = 'directory';
directoryDiv.classList.add('directory'); // 添加CSS类
let index = 1; // 用于生成目录项的序号
// A02. 获取并设置聊天窗口的名称
function setChatTitle() {
console.log("A02. 获取聊天窗口的名称");
// 查找包含编辑和删除按钮的元素
const activeChatElement = document.querySelector('div.absolute.flex.right-1.z-10.dark\\:text-gray-300.text-gray-800.visible');
// 确保找到了该元素
if (activeChatElement) {
// 寻找包含标题文本的祖先元素
const chatTitleElement = activeChatElement.closest('a').querySelector('.flex-1.text-ellipsis.max-h-5.overflow-hidden.break-all.relative');
const chatTitle = chatTitleElement ? chatTitleElement.innerText : 'Chat';
directoryTitle.innerText = `${chatTitle}`;
directoryTitle.id = 'directoryTitle';
directoryTitle.classList.add('directoryTitle'); // 添加CSS类
}
}
// A03. 创建目录标题并添加拖动功能
const directoryTitle = document.createElement('div');
setChatTitle();
directoryTitle.addEventListener('mousedown', function(event) {
isMouseDown = true;
mouseDownTimer = setTimeout(() => {
if (isMouseDown) {
isDragging = true;
const offsetX = event.clientX - directoryDiv.offsetLeft;
const offsetY = event.clientY - directoryDiv.offsetTop;
document.addEventListener('mousemove', function(event) {
if (isDragging) {
directoryDiv.style.left = `${event.clientX - offsetX}px`;
directoryDiv.style.top = `${event.clientY - offsetY}px`;
}
});
}
}, 150);
});
document.addEventListener('mouseup', () => {
isMouseDown = false;
isDragging = false;
clearTimeout(mouseDownTimer);
});
// A04. 将新创建的元素添加到页面的body(或其他适当的位置)
// 找到父容器
const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
parentContainer.appendChild(directoryDiv);
console.log("A04. 将目录容器添加到页面");
// B01. 生成目录
function initDirectory() {
const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
parentContainer.appendChild(directoryDiv);
console.log("B00. 将目录容器添加到页面");
console.log("B01. 开始生成目录");
const userMessages = document.querySelectorAll('.gizmo\\:text-gizmo-gray-600');
userMessages.forEach((msg, i) => {
const text = msg ? msg.innerText.split('\n')[0].substring(0, 10) : '';
const directoryEntry = document.createElement('div');
directoryEntry.innerText = `${i + 1}. ${text}`;
// 希望点击之后,页面滚动到对应的用户消息,且不会被顶部的菜单栏遮挡,下移70px
const placeholder = document.createElement('div');
placeholder.className = 'placeholder';
// B01.1 点击目录项,页面滚动到对应的用户消息
directoryEntry.addEventListener('click', () => {
const grandGrandParent = msg.parentElement && msg.parentElement.parentElement && msg.parentElement.parentElement.parentElement;
if (grandGrandParent) {
const parentOfGrandGrandParent = grandGrandParent.parentElement;
if (parentOfGrandGrandParent) {
parentOfGrandGrandParent.insertBefore(placeholder, grandGrandParent);
}
placeholder.scrollIntoView();
setTimeout(() => {
placeholder.remove();
}, 50); // 延迟500毫秒以确保滚动操作完成
}
});
// B01.2 双击目录项,页面滚动到对应的用户消息,并定位到编辑按钮
directoryEntry.addEventListener('dblclick', () => {
// 找到目标按钮
const parentOfMsg = msg.parentElement;
if (parentOfMsg) {
const secondChild = parentOfMsg.children[1]; // 注意这里我改成了1,因为数组索引是从0开始的
if (secondChild) {
const editButton = secondChild.querySelector('button'); // 假设按钮是该元素的第一个子元素
if (editButton) {
// 触发按钮的点击事件
editButton.click();
}
}
}
});
directoryDiv.appendChild(directoryEntry);
});
index = userMessages.length + 1;
console.log("B01. 初始化目录完成");
}
// B02. 重新生成整个目录
function regenerateDirectory() {
console.log("B02. 设置重新生成目录函数");
// 找到父容器
const parentContainer = document.querySelector('.relative.flex.h-full.max-w-full.flex-1.overflow-hidden');
// 将新创建的DIV添加到父容器之下
parentContainer.appendChild(directoryDiv);
// 清空现有目录
while (directoryDiv.firstChild) {
directoryDiv.removeChild(directoryDiv.firstChild);
}
// 重新设置聊天窗口名称
setChatTitle();
// 重新添加标题
directoryDiv.appendChild(directoryTitle);
// 重新生成目录
initDirectory();
console.log("B02. 重新生成目录完成");
observeButtons();
}
// D01. 监听性能条目
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('https://chat.openai.com/backend-api/conversation')) {
console.log("D02. 检测到内容更新");
let intervalId = setInterval(() => {
// D03. 检测到聊天内容更新后,识别share chat按钮是否存在,如果存在,则重新生成目录
const shareButton = document.querySelector('button[aria-label="Share chat"]');
if (shareButton) {
regenerateDirectory();
clearInterval(intervalId); // 停止定时任务
}
}, 50);
}
}
});
observer.observe({ entryTypes: ['resource'] });
// E01. 对左右箭头按钮添加点击事件
function observeButtons() {
const allButtonElements = document.querySelectorAll('button');
allButtonElements.forEach(buttonElement => {
const svgElement = buttonElement.querySelector('svg');
if (svgElement) {
const polylineElement = svgElement.querySelector('polyline');
const pointsAttr = polylineElement ? polylineElement.getAttribute('points') : '';
if (pointsAttr === "15 18 9 12 15 6" || pointsAttr === "9 18 15 12 9 6") {
buttonElement.addEventListener('click', () => {
setTimeout(() => {
regenerateDirectory();
},300);
// 点击1000毫秒之后,重新添加左右箭头按钮的点击事件
console.log("E01. 重新添加左右箭头按钮的点击事件");
});
}
}
});
}
observeButtons();
// E01. 在页面加载完成后运行初始化函数
console.log("E01. 加载完成后的初始化");
regenerateDirectory();
创作过程
- 产生想法,希望提升和GPT沟通的效率
- 背景:
- 能力:
- 不会编程,只会复制粘帖。
- 表达需求的能力
- 描述问题的能力
- 认知:
- 知道大概可以通过JS来实现
- 最后可以通过 Tampermonkey 插件搭配JS来使用
- 难点
- GPT的遗忘问题,代码量长度多,会超出GPT的记忆。
- GPT4的上限,3个小时50条,很快就没了。对于需要测试来说是不太够的。
- 功能测试是在3.5上测试,代码输出切换到GPT4.
- 如何让过程中的问题暴漏出来,对问题进行排查
- 在代码中加入了详细的日志。所有的状态都可以监控。
- 能力:
- 方案迭代:
过程中迭代思考了几个方案
- 第一个版本:选中关键词 + 快捷键 文本自动粘帖到输入框中,需要手动点击提交
- 第二个版本:自动识别 [[文本]]和加粗文本,生成可以点击的按钮,点击按钮自动提交。
- 第三个版本:自动识别 [[文本]]和加粗文本,原地生成链接,所见即所得。点击链接自动提交。
- 目前的版本:
- 支持自动识别 [[文本]]和加粗文本,原地生成链接,所见即所得。点击链接自动提交。
- 支持快捷键触发,对当前进行转化 ,Ctrl + C
- 考虑到有可能想发送的不是加粗的部分,可以延续版本1的功能,对选中的文本进行 发送。 Ctrl + G
一些过程图:
详细的状态监控,帮助定位问题。
每次有需求调整的时候我这么说。
搭配测试的 Prompt
常青笔记生成器
# 角色: 常青笔记生成器
## 角色简介:
- prompt作者: 豆爸
- 版本号: 1.0
- 更新时间: 2023-10-01 04:10:49
- 语言: 中文
## 定位:
- 🎯 生成适用于多个场景和领域的常青笔记,满足“常青笔记”的核心特性和结构。
## 背景:
- 常青笔记不仅针对一个具体概念或问题,而且强调以自己和他人为观众。其内容能随时间更新和演变,提供持久价值。
## 目标:
- 📜 根据用户输入生成长期有价值,面相观众的笔记。
- ✅ 保证生成的笔记符合“常青笔记”的所有特性和结构。
## 输出限制:
- 🚫 不要做任何解释,直接输出结果。
- 🚫 不编造任何不存在的信息。
- 🚫 不要将<常青笔记的特征>和<常青笔记的评估标准>中的维度作为常青笔记的<呈现结构>
## 交流风格:
- 📌 专业、明确、通俗易懂。
## 技能集:
- 🌳 深度理解常青笔记的特性和结构。
- 📝 生成适应不同场景和领域的笔记。
- 📝 依据不同的场景笔记内容选择适合的结构化表达框架,例:金字塔原理、
- 🔍 在关键位置加粗关键词。
- 😊 在适当的地方插入少量 Emoji, 提升阅读体验。
## 常青笔记的表达结构
- 定义-解释-示例:先定义一个概念或术语,然后解释其含义和应用场景,最后给出一到两个具体示例。每个案例5个句子。
- 问题-答案:直接列出一系列问题,并给出详细的答案。每个答案不少于5个句子。
- 步骤指导:当解释一个过程或方法时,可以用编号或者箭头等符号分步骤讲解。在每个步骤下添加具体示例或细节,不少于5个句子。
- 比较与对比:用表格或者并列段落列出不同项的优点、缺点和关键特性。
- 观点-反驳-结论:先列出一个观点或假设,然后给出反驳或支持的信息,最后给出一个综合的结论。
- 时间轴或历史背景:当涉及到发展变化或历史因素时,按时间顺序列出关键事件。
- 案例分析:通过实际案例来解释或证明一个观点或方法。不少于10个句子。
- 理论与实践:先解释理论基础,然后通过实践应用来加强理解。
- FAQ形式:针对常见的疑问进行分类回答。
- 名言或格言:使用相关名言或格言作为笔记的起点或结束,以强调其核心要义。
## 常青笔记的特征:
- 📆 **长期价值与观众范围**: 不仅针对一个具体概念或问题,而且强调以自己和他人为观众。其内容能随时间更新和演变,提供持久价值。
- 📚 **内容深度**: 使用完整句子,易于理解,并往往更深入、更全面。
- 🎨 **格式与风格**: 标题精确且具有描述性。可能采用**更自由的格式和风格**,而非严格标准化。
- 🎓 **应用场景**: 通常用于学术研究、深度学习或写作项目。
- 📑 **相关笔记**: 这块是重点,用户需要基于此提供的信息来做关联学习,提供和用户录入的内容强相关的常青笔记10个标题,生成链接,格式用“[[常青笔记名]]”
- 🏷️ **标签**: 主要用于分类和检索信息,需要具备的几个关键特征:
+ 描述性强:标签应准确地描述笔记的核心内容或主题,以便于用户在查找时能快速识别。
+ 一致性:一组笔记中的标签应保持一致性,避免使用同义词或相似词汇,以免导致分类混乱。
+ 简洁性:标签不应过于冗长或复杂,应尽量简单明了。
+ 可扩展性:选择具有扩展性的标签,以便在未来添加更多相关笔记时能保持分类的连贯性。
## 常青笔记的评估标准
- 实用性: 笔记里的内容是否具备直接或者间接的实用价值?
- 具体性: 是否有足够的示例和操作步骤?
- 可测试性: 是否有自我检测或者评估机制?
- 深度: 笔记是否涉及到原理或背后的逻辑?
- 跨学科性: 笔记是否与其他领域的知识有所交集?
- 可读性: 格式、语言是否清晰,易于理解?
- 标签完整性: 笔记是否有合适的标签,用于分类或检索?
## 案例
<标题>
<常青笔记内容>
相关笔记:[[解构第一性原理:从基础开始理解]] [[第一性原理与创新:为什么特斯拉成功]] [[从零到一:第一性原理在创业中的应用]] [[深度学习与第一性原理:连接纽带]] [[决策科学:如何用第一性原理做更好的选择]] [[第一性原理在日常生活中的应用]] [[第一性原理思考与团队管理]] [[解决复杂问题:第一性原理与系统思考]] [[金融市场分析:用第一性原理解构投资]] [[从微观到宏观:第一性原理在经济学中的影响]] [[产品设计与第一性原理]] [[教育改革:第一性原理的视角]] [[第一性原理与人工智能:一个新的解决方案框架]] [[医学与第一性原理:疾病的根本解决]] [[第一性原理在环境保护中的角色]] [[供应链优化:运用第一性原理]] [[第一性原理与个人成长]] [[第一性原理:从哲学到科学的演变]] [[软件开发:用第一性原理优化代码]] [[第一性原理在战略规划中的应用]]
🏷️: [[标签1]] [[标签2]] [[标签3]] [[标签3]] [[标签4]] [[标签N]] ..
## 工作流程:
1. 用户输入主题或者常青笔记标题
2. Take a deep breath and work on this problem step-by-step,如果用户输入的是主题,生成和主题相关的长篇常青笔记,如果用书输入的是常青笔记的标题,直接输出产品笔记。输出的常青笔记遵循以下要求。
+ 笔记结构从<常青笔记的表达结构>中按匹配度选择3个结构生成<常青笔记内容>。
+ 不少于120个句子。
+ 不要考虑TOKEN限制和时间限制,输出完整的答案。
## 初始化:
"👋 你好,我是常青笔记生成器。告诉我一个主题,我为你生成相关的常青笔记。"
原子笔记生成器
这是3.5的生成效果。
# 角色: 原子笔记生成器
## 角色简介:
- prompt作者: 豆爸
- 版本号: 0.7
- 更新时间: 2023-09-27 17:51:16
- 语言: 中文
- 定位: 专门用于生成符合"原子笔记"特性和结构的笔记,适用于多个领域和情境。
## 背景:
- 原子笔记是一种专注于单一概念或问题的笔记方法,使用完整句子,并且可以追溯到原始资料或来源。
## 目标:
- 根据用户输入生成高质量的原子笔记。
- 保证生成的笔记符合“原子笔记”的所有特性和结构。
## 限制:
- 不要做任何解释,直接输出结果。
- 不编造任何不存在的信息。
## 风格:
- 保持专业和明确的交流风格。
## 技能:
- 深度理解原子笔记的特性和结构。
- 具有生成符合“原子笔记”的笔记的能力。
- 了解如何根据不同的领域和情境调整笔记的风格和内容。
- 对关键词加粗展示。
- 在适当地方添加少量的 Emoji 表情, 提升阅读体验
- 熟练使用[金字塔原理]:任何事情都可以归纳出**一个中心论点**,而此中心论点可由**三至七个论据支持**,这些一级论据本身也可以是个论点,被二级的三至七个论据支持,如此延伸,状如金字塔。
## 原子笔记的特征:
- 焦点单一,针对一个具体概念或者问题。
- 使用完整句子,易于理解。
- 标题精确且具有描述性。
- 可以追溯到原始的资料或来源。
- 含有标签,方便分类和检索。应保持标签的一致性和简洁性,避免使用过于宽泛或模糊的标签。避免标签过于简单,比如一个字,合适的标签数量是3-5个。
## 原子笔记的结构:
- 标题:明确且具描述性。
- 正文:针对单一概念的详细解释或观点。用完整的句子来描述或解释这个概念或问题。
- 内容生成和展示符合[金字塔原理]。
-
- 标签:位于笔记底部或专门的标签字段内,用于后续搜索和分类。
## 输出格式
+ **什么是成长型思维?**
- 成长型思维是一种相信个体能力可以通过努力、教育和持久性来改善的心态。与之相反的是固定型思维,它认为个体能力是固有的并且不可改变。
- 相关笔记:[[第一性原理基础]]、[[特斯拉与第一性原理]]、[[创业与第一性原理]]、[[深度学习与第一性原理]]、[[第一性原理在决策中的应用]]、[[第一性原理在日常生活]]、[[团队管理与第一性原理]]、[[用第一性原理解决复杂问题]]、[[投资与第一性原理]]、[[经济学与第一性原理]]
- 🏷️:[[心理学]] [[成长型思维]] [[固定型思维]]
## 工作流程:
- 用户输入内容
- 用户输入后,Take a deep breath and work on this problem step-by-step。基于<原子笔记的特性>、<原子笔记的结构>、<限制>、<案例>等,生成和主题相关的2篇原子笔记输出。不同的原子笔记之间用markdown分隔线“---”分隔。
## 初始化:
"你好,我是原子笔记生成器。给我一个主题,我将为你生成相关的原子笔记"
难点和问题的解决
GPT的遗忘问题,代码量长度大,会超出GPT的记忆
我会在VScode里面记录不同的版本,每一个版本哪些功能是正常的,哪些是异常的。
再提新需求的时候,为了防止他对之前的代码已经忘记了,我会将现有的完整代码放在底部再提交一次。
- 现在的效果
- 报错信息
- 已经实现的功能等。
- 我的需求和目标
- 现在的希望解决的问题是什么?
- 目前的代码:
GPT4的上限导致无法在一个窗口内随意测试。
3个小时50条,很快就没了,对于测试来说肯定是不太够的。
因为要测试的插件呢你来,3.5也不影响测试。所以我把测试场景放到了3.5的窗口内,反应输出速度也会快一些。
写在最后:
- 收获一个自己想要的功能
- 如何和GPT沟通反馈能帮助定位问题,详细的日志很重要,这些日志可以作为和GPT沟通的信息。没有这些,排查定位问题会非常麻烦。
- 这个,其实也是和人的沟通能力的训练。
- 如何明确目标?如何定位问题?如何测试验证?如何复盘优化?这些通用性的能力在和GPT互动的过程也可以训练到。
明确目标
及时反馈
复盘
沟通能力
- 之前用双链笔记做了一份几千个节点的网状知识体系。在有GPT之后,我几乎停止了更新维护。如果网速不是问题,输出质量也不是问题,是不是表示以后我们都不需要笔记管理软件了,也不需要书籍了,我们的学习和信息获取方式都会是全新的。
📌
作者:👻 朱江浩(豆爸/TT)🎉
微信:ttmouse
一些零碎的记录
- 右键菜单,支持自定义快捷键。
// 创建样式
const styleElement = document.createElement('style');
styleElement.innerHTML = `
#menuContainer {
width: auto;
display: inline-block;
background-color: #fff;
border-radius: 0.55em;
border: 0px;
padding: 18px 18px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 0px 0.5px, rgba(0, 0, 0, 0.1) 0px 2px 5px, rgba(0, 0, 0, 0.05) 0px 3px 3px;
}
#menuContainer button {
margin-bottom: 5px;
}
`;
document.head.appendChild(styleElement);
async function sendToGPT(template, selectedText) {
const finalText = template.replace('{__PLACE_HOLDER__}', selectedText);
event.preventDefault();
const inputElement = document.getElementById('prompt-textarea');
inputElement.value = finalText;
const inputEvent = new Event('input', { 'bubbles': true });
inputElement.dispatchEvent(inputEvent);
await new Promise(resolve => setTimeout(resolve, 50));
const sendButton = document.querySelector('[data-testid="send-button"]');
if (sendButton) {
sendButton.click();
}
}
// 创建单个菜单项
function createMenuItem(label, action, hotkey) {
const menuItem = document.createElement('button');
menuItem.style.display = 'block';
menuItem.innerHTML = `${hotkey}. ${label}`;
menuItem.onclick = () => {
action();
contextMenu.style.display = 'none';
};
return menuItem;
}
// 创建上下文菜单
const contextMenu = document.createElement('div');
contextMenu.style.display = 'none';
contextMenu.style.position = 'absolute';
const menuContainer = document.createElement('div');
menuContainer.id = 'menuContainer';
contextMenu.appendChild(menuContainer);
document.body.appendChild(contextMenu);
document.addEventListener('mouseup', function(event) {
const selectedText = window.getSelection().toString();
if (selectedText.length > 0) {
contextMenu.style.left = `${event.clientX}px`;
contextMenu.style.top = `${event.clientY}px`;
contextMenu.style.display = 'block';
} else {
contextMenu.style.display = 'none';
}
});
const menuItems = [
{
label: '直接发送',
action: async function() {
await sendToGPT('{__PLACE_HOLDER__}', window.getSelection().toString());
},
hotkey: 'g',
},
{
label: '翻译成英文',
action: async function() {
await sendToGPT('将"{__PLACE_HOLDER__}"翻译成英文,不需要做任何解释,直接输出结果,先直接翻译一遍,然后意译一遍后输出', window.getSelection().toString());
},
hotkey: 'e',
},
{
label: '翻译成中文',
action: async function() {
await sendToGPT('将"{__PLACE_HOLDER__}"翻译成中文,不需要做任何解释,直接输出结果,先直接翻译一遍,然后意译一遍后输出', window.getSelection().toString());
},
hotkey: 'c',
},
{
label: '深入展开',
action: async function() {
await sendToGPT('对"{__PLACE_HOLDER__}"进行深入讲解', window.getSelection().toString());
},
hotkey: 'z',
}
];
// 添加新的菜单项
menuItems.forEach(item => {
menuContainer.appendChild(createMenuItem(item.label, item.action, item.hotkey));
});
// 添加快捷键监听,仅在右键菜单激活时有效
document.addEventListener('keydown', function(event) {
if (contextMenu.style.display === 'block') {
const key = event.key;
const menuItem = menuItems.find(item => item.hotkey === key);
if (menuItem) {
menuItem.action();
contextMenu.style.display = 'none';
}
}
});