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-Type 是 text/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)}`

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

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

总结

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

相关推荐
小码哥_常5 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
蜗牛攻城狮7 小时前
微前端四大方案选型对比与最佳实践:qiankun、micro-app、wujie、Module Federation
前端·微前端·qiankun·前端架构·micro-app·wujie
她说可以呀8 小时前
Vue插槽
前端·javascript·vue.js
qq_448011168 小时前
Vue + Jinja2两套模版与法冲突造成undefinedError
前端·javascript·vue.js
aixingpan9 小时前
aixingpan.cn API开发文档:前端查询夏令时和时区接口指南
前端·php
2601_958352909 小时前
23.5×19mm 换掉一整条音频链?|A-68 集成了什么:ADC / DAC / 24.576MHz 时钟 / AEC / 波束降噪,主控仅需 6 根线
前端·音视频·ai降噪·回声消除·语音模块
志尊宝10 小时前
AI原生前端开发(039):AI辅助的Nuxt 4全栈开发
前端·vue.js·ai-native
FRing0311 小时前
JavaScript入门到精通三十二(深拷贝、浅拷贝、递归函数)
开发语言·前端·javascript
excel11 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放11 小时前
Python基础语法练习题(73)
java·前端·python