
几天前,我创建了管家AI,允许你在任何网站上使用ChatGPT,只需输入管家:无论你想做什么 它是一个Chrome浏览器的扩展,在许多不同的网站上都能正常工作。
你可以在下面的图片中看到它是如何工作的。

在收到对它的热烈反应后,我决定围绕它创建一个简单的教程,说明它是如何工作的,以及你如何能够创建一个属于你自己的类似扩展。
因此,让我们开始吧。
设置Chrome扩展
Chrome目前建议使用Manifest V3来定义任何扩展,我们将使用同样的方法。manifest.json ,这是定义任何Chrome扩展的配置文件。
在你的目录中定义一个manifest.json ,并在其中添加以下内容。
{
"name": "Butler AI - Powered by ChatGPT",
"description": "Use the power of ChatGPT at your fingertips, The butler will serve its master.",
"author": "Prashant Yadav",
"version": "0.0.1",
"manifest_version": 3,
"permissions": ["storage", "activeTab"],
"host_permissions": [""],
"action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [""],
"runAt": "document_end",
"js": ["script.js"],
"all_frames": true
}
]
}
进入全屏模式 退出全屏模式
很多东西都是不言自明的,让我们对重要的属性做一个回顾。
- 权限:这定义了这个Chrome扩展能够访问的所有东西,我们希望能够访问
activeTab,以观察命令中的内容,并对此作出回应;storage,以访问localStorage,并存储一些秘密,如ChatGPT API密钥。 - 动作:default_popup:当你点击扩展的ICON时打开的默认HTML页面。
- content_scripts:这定义了当新标签打开时要加载哪个javascript文件,以及何时运行这个文件。基本上,我们将在
document_end(当页面加载完成时)打开script.js,对于所有的URLsall_urls,在所有的框架Iframes。在这个script.js,我们所有的逻辑都会出现。
popup.html
写一个简单的信息,只是为了确保popup.html 正在正常加载。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Butler AI</title>
</head>
<body>
<h1>Butler AI Powered By ChatGPT</h1>
</body>
</html>
进入全屏模式 退出全屏模式
script.js
打印一条信息以检查脚本是否被正确注入。
console.log("ButlerAI");
进入全屏模式 退出全屏模式
加载Chrome扩展程序
现在我们的模板已经准备好了,让我们加载扩展,看看它是否工作正常。
记住,我们必须在本地机器上的开发者模式下加载。
- 打开Chrome浏览器。
- 进入设置>扩展。
- 启用右手边上角的开发者模式。
- 点击左侧上角的 "加载未打包按钮"。
- 导航并加载你的目录。
一旦你加载了扩展,打开一个新的标签,导航到StackEdit,在控制台中,应该打印出信息ButlerAI 。
观察被写在任何网页上的文本
在本教程中,我将在StackEdit上运行这个扩展,它是一个流行的标记编辑器。现在扩展已经加载,在script.js ,我们将不得不观察用户正在输入的内容,并找到上下文管家中的任何内容**:无论什么命令;**是否被写入。
为了做到这一点,我们将监听整个窗口(activeTab)的按键事件,当用户停止打字时,我们将解析HTML,寻找任何以butler开头、以**;**结尾的文本。
观察用户在活动标签上输入的内容
这一点我们将在debounced事件中进行,因为我们只想在用户停止打字时进行搜索,因此debouncing是个好办法。
// helper function to debounce function calls
function debounce(func, delay) {
let inDebounce;
return function () {
const context = this;
const args = arguments;
clearTimeout(inDebounce);
inDebounce = setTimeout(() => func.apply(context, args), delay);
};
}
// debounced function call
const debouncedScrapText = debounce(scrapText, 1000);
// observe what the user is typing
window.addEventListener("keypress", debouncedScrapText);
进入全屏模式 退出全屏模式
在这里,scrapText 函数将在1000毫秒后被弹出,也就是说,如果用户停止打字1秒,那么只有scrapText 函数会被调用。
寻找以butler开头的文本
我们首先要收集页面上的所有文本,然后检查哪些文本以**butler:开头,以;**结尾。如果发现任何这样的文本,那么我们将存储包含这个文本的HTML节点,这样我们将用命令的响应来填充它,同时也提取出命令。
为了缩小搜索范围,我们不会解析整个页面,而只会解析接受文本的HTML元素。例如,用户可以键入或提供输入的地方。
在StackEdit上,用户可以书写的区域有一个属性contenteditable="true" ,因此我们可以得到这个元素,得到它的文本并解析它们。
// regex to check the text is in the form "butler: command;"
const getTextParsed = (text) => {
const parsed = /butler:(.*?)\;/gi.exec(text);
return parsed ? parsed[1] : "";
};
// helper function to get the nodes, extract their text
const getTextContentFromDOMElements = (nodes, textarea = false) => {
if (!nodes || nodes.length === 0) {
return null;
}
for (let node of nodes) {
const value = textarea ? node.value : node.textContent;
if (node && value) {
const text = getTextParsed(value);
if (text) return [node, text];
else return null;
}
}
};
// function to find the text on active tab
const scrapText = () => {
const ele = document.querySelectorAll('[contenteditable="true"]');
const parsedValue = getTextContentFromDOMElements(ele);
if (parsedValue) {
const [node, text] = parsedValue;
makeChatGPTCall(text, node);
}
进入全屏模式 退出全屏模式
在这里,我们得到所有的HTML元素,提取它们的文本,并检查它们是否与我们所期望的模式相匹配,一旦它们相匹配,我们就得到那个节点(HTML元素)和命令。
不同的网站有不同的接受输入的方式,因此你会看到,我正在检查节点是否是textarea ,并相应地得到它的值。
一旦我们有了它们,我们就会把它们传递给ChatGPT的API调用。
用ChatGPT API获取命令响应
创建这个函数,它将接受命令和节点,并将ChatGPT API对该命令的响应填充到节点中。
我们将使用ChatGPT的完成度API与text-davinci-003模型。你可以根据你的喜好使用任何一个API和模型,但要记住你在免费层中的代币是有限的,所以在测试时要注意不要用尽限制。通过这个ChatGPT游乐场探索你的选择。
你必须在授权头文件中传递你的API密钥,使其发挥作用。
const makeChatGPTCall = async (text, node) => {
try {
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", `Bearer ${apikey}`);
// set request payload
const raw = JSON.stringify({
model: "text-davinci-003",
prompt: text,
max_tokens: 2048,
temperature: 0,
top_p: 1,
n: 1,
stream: false,
logprobs: null,
});
// set request options
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow",
};
// make the api call
let response = await fetch("https://api.openai.com/v1/completions", requestOptions);
response = await response.json();
const { choices } = response;
// remove the spaces from the reponse text
const text = choices[0].text.replace(/^\s+|\s+$/g, "");
// populate the node with the response
node.textContent = text;
} catch (e) {
console.error("Error while calling openai api", e);
}
};
进入全屏模式 退出全屏模式
就这样,重新加载扩展,看看它的神奇之处,它在StackEdit上的工作应该是很有魅力的。
这个扩展最具挑战性的部分是在用户写的时候从不同的网站上读取数值,然后用响应填充回来。为了安全起见,网站会做很多内部的事情,并通过javascript改变内容来阻止值的更新。
我在许多网站上做了这个工作。你可以花10美元获得Butler-AI的源代码,但我把它留给你,让你尝试让它在尽可能多的网站上工作。
你也可以在我的youtube频道上观看该教程。
另外,请在Twitter上关注我,了解解决编码面试的技巧和窍门,以及更多算法的解题实例。我每周在我的博客learnersbucket.com上写2-3篇文章。
