VSCode插件开发五:文件上的ChatGPT打字效果

在上一篇中,我们介绍了如何向文件中添加内容。然而,这只是通过简单的示例来演示,并没有真正应用到实际场景中。为了更好地理解这些API并进行实际操作,让我们尝试一下。我们将利用WorkspaceEdit对象实现一个文件上的ChatGPT打字效果,相较于一次性写入文件内容,这个功能会稍微复杂一些。实现这个功能的流程首先是发起请求,然后清空全部或选定内容,再逐步插入字符。下面我们来看一下具体的实现过程。

ChatGPT 数据请求

首先,由于VSCode插件使用的是Node.js,我们可以使用axios来进行数据请求。我们需要监听data事件来接收ChatGPT的流数据,并监听end事件来表示结束。

代码如下:

js 复制代码
import axios from 'axios'

axios
  .post(
    'https://api.openai.com/v1/chat/completions',
    {
      model: 'gpt-3.5-turbo',
      messages: [
        {
          role: 'user',
          content: 'Hello!',
        },
      ],
      stream: true,
    },
    {
      headers: {
        'content-type': 'application/json',
        Authorization: `Bearer sk-xxx`,
      },
      responseType: 'stream',
    }
  )
  .then((res) => {
    res.data.on('data', (chunk) => {
      // 处理接收的数据
    })
    res.data.on('end', (chunk) => {
      // 接收完成处理
    })
  })

清空内容

清空内容需要使用WorkspaceEdit对象中的delete方法。我们可以根据selection对象判断是否存在选定文本。如果selection是空的(即没有选定文本),则创建一个新的vscode.Range对象,表示要删除整个文档内容。

js 复制代码
const selection = activeEditor.selection
const deleteRange = selection.isEmpty ? new vscode.Range(0, 0, activeEditor.document.lineCount, 0) : selection
const workspaceEdit = new vscode.WorkspaceEdit()
workspaceEdit.delete(activeEditor.document.uri, deleteRange)
await vscode.workspace.applyEdit(workspaceEdit)

插入内容

插入内容需要使用WorkspaceEdit对象中的insert方法。插入字符的位置只需要给定起始位置,可以通过传递字符偏移量的数字,并使用document.positionAt方法将其转换为Position对象。每次插入内容后,起始位置也会随之增加。

那为什么我们不使用activeEditor.selection.start来获取起始位置呢?因为如果没有选定内容,activeEditor.selection.start会返回光标的位置。每次插入内容后,光标也会随之移动。然而,这种不确定性太多了,例如切换页面时光标位置不会改变,或者操作中意外触发可能导致内容错乱。

js 复制代码
function insertContent(activeEditor: vscode.TextEditor, content: string, start = 0) {
  return new Promise<void>((resolve, reject) => {
    const workspaceEdit = new vscode.WorkspaceEdit()
    workspaceEdit.insert(activeEditor.document.uri, activeEditor.document.positionAt(start), content)
    vscode.workspace.applyEdit(workspaceEdit).then(
      (success) => {
        resolve()
      },
      (err) => {
        reject()
      }
    )
  })
}

在这个持续插入内容的过程中,我们需要考虑并发的问题。也就是说,在上一个插入操作完成之前,我们需要等待它完成才能继续插入。代码如下:

js 复制代码
const datas = chunk.toString().split('\n\n')
datas.forEach((list: string) => {
  const data = list.replace('data: ', '')
  if (data.startsWith('{')) {
    const json = JSON.parse(data)
    let deltaContent = json.choices[0].delta?.content || ''
    if (activeEditor.document.eol === 2) {
      deltaContent = deltaContent.replace(/\n/g, '\r\n')
    }
    content += deltaContent
    if (adding || deltaContent.length === 0) {
      return
    }
    adding = true
    insertContent(activeEditor, content, start).then(() => {
      adding = false
    })
    start += content.length
    content = ''
  }
})

这段代码在data事件的处理逻辑中。我们使用adding变量来控制并发,这个变量在data事件外部进行定义。

判断activeEditor.document.eol === 2表示编辑器换行格式为CRLF,对应字符是\r\n。而ChatGPT使用的是\n。如果换行符不统一,计算字符的偏移量就会出现差异,导致内容错乱。

start += content.lengthcontent = ''都需要放在insertContent异步函数的外部。如果不这样做,随着content的增加,起始位置的计算也会变得不准确。


以上就是实现打字效果的流程。虽然看起来很简单,但是其中有很多细节需要注意。

这个在我的Markdown插件已经实现,有兴趣可以去看看Markdown Joy - Visual Studio Marketplace

相关推荐
SchuylerEX5 分钟前
第六章 JavaScript 互操(2).NET调用JS
前端·c#·.net·blazor·ui框架
东风西巷1 小时前
Rubick:基于Electron的开源桌面效率工具箱
前端·javascript·electron·软件需求
探码科技1 小时前
AI知识管理软件推荐:九大解决方案与企业应用
前端·ruby
编程小黑马1 小时前
解决flutter 在控制器如controller 无法直接访问私有类方法的问题
前端
Miracle_G2 小时前
每日一个知识点:JavaScript 箭头函数与普通函数比较
javascript
unfetteredman2 小时前
Error: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found
前端·javascript·vite
云存储小精灵2 小时前
Dify x 腾讯云 COS MCP:自然语言解锁智能数据处理,零代码构建 AI 新世界
前端·开源
山间板栗2 小时前
微信小程序环境变量设置方案
前端
电商API大数据接口开发Cris2 小时前
Java Spring Boot 集成淘宝 SDK:实现稳定可靠的商品信息查询服务
前端·数据挖掘·api
pepedd8642 小时前
LangChain:大模型开发框架的全方位解析与实践
前端·llm·trae