Chrome 插件开发实战:实现一个对照翻译插件(二)翻译部分

前言

上篇文章 介绍了插件的前端部分,这篇我们来介绍怎么对提取出来的内容进行翻译。生活在 ChatGPT 的时代,当然要好好利用一下这个工具了,所以本文就用它来进行翻译了。

接入 ChatGPT

本文没有采取官方 API_KEY 的方式,而是需要用户先自行登录 ChatGPT,然后利用跨站请求会自动携带 cookie 这一特性先请求 openai 的 /api/auth/session 接口获取 accessToken,然后使用其来发起对话,核心代码如下所示:

js 复制代码
  async sendMessage(params: {
    prompt: string
    onEvent: (p: OnEventParams) => void
  }) {
    if (!this.accessToken) {
      this.accessToken = await this.getAccessToken()
    }

    const rsp = await fetch(
      'https://chat.openai.com/backend-api/conversation',
      {
      ...
      }
    )

    this.parseSSEResponse(rsp, (message) => {
      console.log('msg', message)
      ...
    })
  }

由于 conversation 返回的 Content-Typetext/event-stream 类型的,所以本文使用了 eventsource-parser 来进行解析:

js 复制代码
async parseSSEResponse(resp: Response, onMessage: (message: string) => void) {
  if (!resp.ok) {
    const error = await resp.json().catch(() => null)
    if (!error) {
      throw new Error(JSON.stringify(error))
    }
    throw new Error(`${resp.status} ${resp.statusText}`)
  }
  const parser = createParser((event) => {
    if (event.type === 'event') {
      onMessage(event.data)
    }
  })
  const decoder = new TextDecoder()
  for await (const chunk of this.streamAsyncIterable(resp.body!)) {
    const str = decoder.decode(chunk)
    parser.feed(str)
  }
}

async *streamAsyncIterable(stream: ReadableStream) {
  const reader = stream.getReader()
  try {
    while (true) {
      const {done, value} = await reader.read()
      if (done) {
        return
      }
      yield value
    }
  } finally {
    reader.releaseLock()
  }
}

注意,这里的 async *streamAsyncIterable 是个 AsyncGenerator,感兴趣的可以自行学习。

但是,直接在 content.js 中运行上述代码是会跨域的,因为我们插件的 content.js 其实是运行在其他网页之中,那么怎么解决呢?答案就是代理。

首先,从插件的页面发起请求到 openai 是不会跨域的,所谓插件的页面就是类似于这样的页面:chrome-extension://dhchbfllfandgngcnkinfbdhehchbhch/index.html,其中 // 后面那一串是插件的唯一 id。所以,我们可以在网页中插入一个不可见的 iframe 来作为 Proxy,通过 postMessage 让其跟 content.js 互相进行通信:

好了,搞定了 ChatGPT 的接入,接下来就是翻译了。

翻译

上篇文章中我们已经提取出了待翻译的内容,并且按照段落组织好了,所以,接下来的任务就轻松了。我们先把每个段落的待翻译文本收集成一个数组:

js 复制代码
paragraphs.forEach(
  ({commonAncestorEl, text, translationWrapperEl, translationEl}) => {
    commonAncestorEl.appendChild(translationEl)
    texts.push(text.trim())
  }
)
const results = await this.requestAI(texts)

接下来我们写一个这样的 Prompt,发送给 ChatGPT:

json 复制代码
`I will give you a JSON array, please translate each item from ${from} to ${to} and
return a JSON array whose items are translation result string, please return the JSON directly,
here is my JSON array: ${JSON.stringify(texts)}`

其中,文本中 fromto 分别是原文语言和目标语言。然后,我们把得到的翻译结果解析出来,并显示到页面上即可:

js 复制代码
paragraphs.forEach(({translationEl}, index) => {
  translationEl.innerText = results[index]
  translationEl.classList.remove(this.loadingClass)
  translationEl.classList.add(this.translationClass)
})

总结

我们通过两篇文章介绍了如何实现一个并行翻译的 Chrome 插件,实测发现,翻译的速度有点慢,即使我们做了只翻译视口中的内容的优化。但其实还有优化空间,比如我们现在是等所有的文本都翻译好以后再一起返回结果,但 openai 的返回是流式的,我们也可以流式地进行处理。之前在编译原理之手写一门解释型语言中介绍的状态机貌似可以派上用场,即我们可以通过一个状态机来不停地匹配翻译后的文本 token,这个后面有空再优化了。最后,欢迎关注公众号"前端游"。

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax