耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

前语

很多人应该都运用过微软的 Chat Bing。当运用 Bing 进行查找的时分,不仅会展现检索的成果页,并且还会在浏览页面的侧边栏显示相似于 ChatGPT 的呼应。这个时分查找内容其实是作为 prompt 输入给 Chat Bing。如下图所示:

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

于是,我想着可不能够在百度或谷歌浏览器中也完成这样的作用。最直接的一个主意便是开发一个浏览器插件,赋予其 GPT 的才能。

除此之外,我还想添加一些额外的功用。每次查找时,咱们都会点击最匹配的成果页,并检查这些成果页是否符合预期。假如咱们把这一步交给 GPT 去做,让它帮咱们总结网页的内容,这样能够节约一些检索的时刻,提高查找功率。

好嘞,简单总结一下需求,有两点:

  • 完成相似于 Chat Bing 的作用。
  • 完成总结网页内容的功用。

需求明晰后,我用了大概一天时刻写了 Search GPT 浏览器插件。

接下来,我带我们看看我的完成进程!

Github 地址如下,其间包括详细的 README 文档

github.com/ltyzzzxxx/s…

技能选型

为了快速开发浏览器插件,我挑选了 Plasmo 结构。首要原因是它支撑 React、Vue 等结构,且能够很便利的引入第三方组件库(如 Ant Design),调试插件的时分也很便利,支撑热布置。假如不运用结构的话,得写原生 html、js、css,很头疼哈哈哈哈~

挑选 React 作为前端开发结构,Ant Design 作为组件库。这儿就不多介绍啦,信任前端开发的同学一定非常熟悉。

为了完成这个总结页面内容这个功用,我选用了 Python + BeautifulSoup 去爬取网站内容,并由 GPT 进行总结。

Beautiful Soup 是一个能够从 HTML 或 XML 文件中提取数据的 Python 库。它能够经过你喜爱的转换器完成惯用的文档导航 / 查找 / 修改文档的办法。Beautiful Soup 会帮你节约数小时乃至数天的工作时刻。

如下为技能选型表:

技能栈 介绍
Plasmo 简化浏览器插件开发,支撑运用 React、Vue 等前端开发结构
React + Ant Design 前端开发结构 + 前端组件库
Python + Flask + BeautifulSoup 后端 Web 结构 + 爬虫

浏览器插件组件

为了便利接下来介绍 Search GPT 的开发进程,我先给我们介绍一下浏览器插件中根本的组件!

在 Search GPT 中,我运用到了 Content Script、Background 与 Popup

  • Content Script:其用于直接与浏览器进行交互,它运行在页面的上下文中,能够读取和修改网页的内容。

    举个 🌰,假如咱们想要完成 Chat Bing 的作用,咱们需求在浏览器的右侧边栏画一个框,框中展现 ChatGPT 对查找内容的呼应。这个 UI 完成需求经过 Content Script 来完成!

  • BackGround Script:顾名思义,它是运行在插件的后台,独立于浏览器的其他网页,一般用于处理全局的状况,接收并处理 Content Script 发出的事情。

    举个🌰,当 Content Script 读取到当时页面是一个查找页(www.google.com/search),它就能够读取查找内容,并以事情的形式发送给 Background。Background 便能够去恳求 GPT Api,获取 prompt 的呼应成果,并回来给 Content Script。

  • Popup:这个组件最简单了解,当咱们点击插件图标时,其会弹出一个框,可能是一个登陆框、设置框等。

代码结构

├── src
│   ├── background.ts
│   ├── backgroundFunctions
│   │   ├── handleCrawlerApi.ts
│   │   └── handleGptApi.ts
│   ├── contents
│   │   ├── BaiduSearchBox.tsx
│   │   ├── GoogleSearchBox.tsx
│   │   ├── PageCollapse.tsx
│   │   └── plasmo.tsx
│   └── popup.tsx
  • background.ts:为 background 脚本文件
  • backgroundFunctions:将 background.ts 中用到的函数抽取到该文件夹中
  • contents:其间 plasmo 为 Content Script,剩下的为 React 组件
  • popup.tsx:弹出框组件

完成计划

存储 API Key

在 Search GPT 中,Popup 用于设置 API Key 密钥,并用 Plasmo 供给的 storage 进行持久化存储。

如下图所示即为 popup 作用。

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

其间的中心代码便是处理 Save 与 Clear 事情。

  • 保存 API Key,首先是验证 API Key 是否正确,然后进行存储,并更新提示状况。

    详细怎么验证 API Key,请检查源码,原理便是调用 Open AI 供给的 api。

    // src/popup.ts 第 43 行
    const saveGptApiKey = async () => {
        if (await verifyGptApiKey(gptApiKey)) {
          try {
            await storage.set("gptApiKey", gptApiKey)
            setGptStatus("GPT API Key saved successfully.")
          } catch (error) {
            setGptStatus("Failed to save GPT API Key.")
          }
        }
    }
    
  • 铲除 API Key,更新提示状况。

    // src/popup.ts 第 54 行
    const clearGptApiKey = async () => {
        await storage.remove("gptApiKey")
        setGptApiKey("")
        setGptStatus("Enter your GPT API Key")
    }
    

中心完成

需求 1

当用户用谷歌或百度查找时,插件会以查找内容作为 prompt,去恳求 GPT。如下为详细流程图。

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

  • 判别页面是经过 Plasmo 结构供给的模板进行装备,只有当网站地址匹配到 google 或 baidu 才会进行之后的流程。

    // src/contents/plasmo.tsx 第 8 行
    export const config: PlasmoCSConfig = {
      matches: ["https://*.google.com/*", "https://*.baidu.com/*"]
    }
    
  • 烘托 GPT 呼应框是经过 document 的办法获取到指定的方位,在该方位烘托对应的 React 组件。

    • 谷歌和百度对应的方位有所不同,所以需求分别处理
    // src/contents/plasmo.tsx 第 30 行
    const displayGptResponse = (responseText: string, hostname: string) => {
      let container = document.getElementById("gpt-response-container")
      if (container) {
        container.remove()
      }
      container = document.createElement("div")
      container.id = "gpt-response-container"
      if (hostname == "www.google.com") {
        let appbar = document.getElementById("appbar")
        appbar.parentNode.insertBefore(container, appbar)
        const root = createRoot(container)
        root.render(<GoogleSearchBox responseText={responseText} />)
      } else {
        const parentElement = document.getElementById("content_right")
        parentElement.insertBefore(container, parentElement.firstChild)
        const root = createRoot(container)
        root.render(<BaiduSearchBox responseText={responseText} />)
      }
    }
    
  • 获取用户输入的内容是经过 window 的办法,获取地址栏中对应的恳求参数。

    // src/contents/plasmo.tsx 第 30 行
    let urlSearchParams = new URLSearchParams(window.location.search)
    const queryParam =
    window.location.hostname == "www.google.com"
      ? urlSearchParams.get("q")
      : urlSearchParams.get("wd")
    
  • 发送事情给 Background 是经过 chrome.runtime.sendMessage 完成的,发送的目标中有两个特点:

    • type:Background 可能会接收到多个事情,需求为事情命名以区分不同事情。
    • query:查找内容,会作为 GPT 恳求的 prompt。

    这儿需求留意的是,当 GPT 恳求成功或失败之后,需求再次烘托 GPT 呼应框,于是再次调用 displayGptResponse 办法。

    // src/contents/plasmo.tsx 第 58 行
    chrome.runtime.sendMessage(
      { type: "fetchGptResponse", query: queryParam },
      (response) => {
        if (response) {
          if (response.type === "gptResponse") {
            displayGptResponse(response.data, window.location.hostname)
          } else if (response.type === "gptError") {
            console.error("GPT Error:", response.error)
            displayGptResponse("Request GPT error...", window.location.hostname)
          } else if (response.type === "apiKeyError") {
            console.error("API Key Error: API Key not set or invalid.")
            displayGptResponse("API Key not set or invalid.", window.location.hostname)
          }
        }
      }
    )
    
  • Background 处理逻辑为判别事情类型。假如为 GPT 恳求事情,则恳求 GPT,并将恳求成果回来给 Content Script。

    如下为恳求 GPT 代码,详细代码请检查 src/backgroundFunctions/handleGptApi.ts

    // src/backgroundFunctions/handleGptApi.ts
    fetch(
      "https://api.openai.com/v1/engines/text-davinci-003/completions",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + apiKey
        },
        body: JSON.stringify({
          prompt: message.query,
          max_tokens: 1024
        })
      }
    )
    

需求 2

当用户点击每个查找成果页下方的 collapse 时,插件会去爬取对应网页的部分内容,然后将这部分内容作为 prompt 去恳求 GPT,总结网页内容。如下为详细流程图。

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

  • 经过 document 获取每个查找成果页所对应的 HTML 元素,并在其下方烘托 collapse 组件。烘托办法与第一个需求相同。

    谷歌查找和百度查找的处理逻辑有细微差别,首要是因为 HTML 元素名不同。以 google 查找举例:

    // src/contents/plasmo.tsx 第 79 行
    let parentPageElement = document.getElementById("rso")
    let i = 0
    for (let child of parentPageElement.children) {
      let targetElement = child.querySelector('[jsname="UWckNb"]')
      if (targetElement != null && targetElement.tagName === "A") {
        console.log("第" + i + "个page", targetElement.href)
        // add collapse
        let link = targetElement.href
        let container = document.createElement("div")
        container.id = "page-collapse-" + i
        const root = createRoot(container)
        child.insertAdjacentElement("afterend", container)
        root.render(<PageCollapse link={link} />)
      }
      i += 1
    }
    
  • 当用户点击 collapse 组件时,会展开对应的内容。此时,Content Script 会发送爬虫事情给 Background。

    // src/contents/PageCollapse.tsx 第 18 行
    const sendMessageToHandleCrawler = () => {
        return new Promise((resolve, reject) => {
          chrome.runtime.sendMessage(
            { type: "fetchCrawler", link: link },
            (response) => {
              if (response) {
                if (response.type === "crawlerSuccess") {
                  console.log("request crawler success", response.data)
                  setLinkContent(response.data)
                  resolve(response.data) // 解析 Promise
                } else if (response.type === "crawlerError") {
                  console.error("request crawler error", response.error)
                  reject(response.error) // 拒绝 Promise
                }
              }
            }
          )
        })
    }
    
  • Background 收到爬虫事情后,会去恳求 Flask 后端。

    发送恳求的部分代码如下,详细代码请检查 src/backgroundFunctions/handleGptApi.ts

    // src/backgroundFunctions/handleGptApi.ts
    fetch("http://localhost:5000/?link=" + encodeURIComponent(message.link))
        .then((resp) => {
          if (resp.status === 200) {
            return resp
              .text()
              .then((text) => sendResponse({ type: "crawlerSuccess", data: text }))
          } else {
            sendResponse({
              type: "crawlerError",
              error: "Cannot crawl the web page."
            })
          }
        })
    

    Python 爬虫代码如下,这儿需求留意两点:

    • 网站内容过大,需求对爬取到的内容做截断,截取前 1000 个字符。
    • 有些网站无法爬取到正常的内容,需求判别内容是否为空
    @app.route('/')
    @use_kwargs({'link': fields.Str(required=True)}, location="query")
    def hello_world(link):
        response = requests.get(link)
        html = response.text
        soup = BeautifulSoup(html, "html.parser")
        text = soup.get_text()
        clean_text = text.strip().replace("\n", " ")[:1000]
        print(link, clean_text)
        if clean_text:
            return Response(clean_text, status=200)
        else:
            return Response('error', status=500)
    
  • Content Script 收到爬虫呼应内容后,会接着给 Background 发送 GPT 恳求事情,用 GPT 总结网页内容,并烘托到 collapse 框中。这儿的处理逻辑与需求 1 相似,就不再赘述啦~

作用展现

关于需求 1,其完成作用如下:

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

关于需求 2,其完成作用如下:

耗时一天写了个 Search GPT 浏览器插件,提高查找功率!

总结

在这篇文章中,我从技能选型、浏览器插件组件、代码结构、完成计划、作用展现等视点,详细介绍了 Search GPT 插件的完成进程。

插件的根本功用是能够正常运用的,需求优化的点在于 GPT 的呼应速度、查找成果页的摘要准确度等方面。

代码现已开源,我们感兴趣的话能够根据 README.md 文档去试用一下这个插件。现在插件还没有发布到 chrome 商铺。

假如运用进程中有 Bug 的话,费事提一下 Issue 呀~

Github 地址:github.com/ltyzzzxxx/s…

今日的内容就到这儿啦,我们觉得有用的话费事帮助点个赞、点个 Star 支撑一下呀,下期再见!