本文作者是360奇舞团开发工程师
引言
OpenAI
发布了ChatGPT
,就像是给平静许久的互联网湖面上扔了一颗重磅炸弹,刹那间所有人都在追捧学习它。究其原因,它其实是一款真正意义上的人工智能对话机器人
。它使用了深度学习技术,通过大量的训练数据和自监督学习方法进行训练,以模拟人类的对话能力和生成自然语言回应。日常生产、学习中利用好ChatGPT
这个工具,是绝对能够提升我们工作效率的,这一点对于我们程序员来说,感受应该尤为明显。我们最常用的开发工具VSCode
,已经有许多的插件集成了ChatGPT
功能,这篇文章将从零开始,介绍这些插件的实现原理与思路,希望对你有所帮助。
基本需求
实现一款可以跟ChatGPT
对话的插件,可以通过一问一答的形式来进行对话,并且可以将我们选中的代码发送给ChatGPT
,让其可以对代码进行优化。当然如果要访问ChatGPT
,首先需要绑定我们在OpenAI
后台申请的ApiKey
.
VSCode 插件基本配置
首先简单介绍一下VSCode
插件开发的基本流程
- 安装脚手架
go
npm install -g yo generator-code
然后cd
到你的工作目录,运行yo code
,根据向导一步步选择即可,没啥好说的,运行完后就生成了一个干净的可以运行的插件工程了。
- 工程目录介绍
查看当前目录,工程的核心是package.json
与extension.js
.首先看下package.json
的配置文件:
-
name
:工程名称 -
displayName
: 应用市场名称 -
description
: 应用描述 -
version
: 当前插件版本 -
engines
: 表示插件最低支持的vscode版本 -
categories
: 插件应用市场分类 -
main
: 程序的主入口文件 -
activationEvents
:重要,扩展的激活事件数组,表示可以被哪些事件激活当前插件。比如:
go
"activationEvents": [
"onView:chatgpt-for-vscode.view",
"onCommand:chatgpt-for-vscode.setAPIKey",
"onCommand:chatgpt-for-vscode.askGPT",
"onCommand:chatgpt-for-vscode.whyBroken",
"onCommand:chatgpt-for-vscode.optimizeCode",
"onCommand:chatgpt-for-vscode.explainCode",
"onCommand:chatgpt-for-vscode.refactor"
],
onView
:表示 通过视图触发,chatgpt-for-vscode.view
是视图Id。当触发这个视图时,唤起当前插件
onCommand
: 表示通过命令触发,后面是命令Id,这些都是我们自定义的命令。在VSCode
中按下快捷键:Command + Shift + P
输入命令title
后唤起插件,命令title
在contributes
,commands
模块里面定义,后面介绍。
除了这两个还有:onLanguage
、onUri
、onDebug
、workspaceContains
、onFileSystem
等,如果设置为*
,只要一启动VSCode
,插件就会被激活,当然为了用户体验,官方不推荐这么做。
contributes
: 重要,配置插件的主要功能点。比如:
go
"contributes": {
"commands": [
{
"command": "chatgpt-for-vscode.setAPIKey",
"title": "GPT:绑定APIKey"
},
{
"command": "chatgpt-for-vscode.askGPT",
"title": "GPT:询问 GPT"
},
{
"command": "chatgpt-for-vscode.whyBroken",
"title": "GPT:说明这段代码存在的问题"
},
{
"command": "chatgpt-for-vscode.optimizeCode",
"title": "GPT:优化这段代码"
},
{
"command": "chatgpt-for-vscode.explainCode",
"title": "GPT:解释这段代码"
},
{
"command": "chatgpt-for-vscode.refactor",
"title": "GPT:重构这段代码"
}
],
"menus": {
"editor/context": [
{
"command": "chatgpt-for-vscode.askGPT",
"group": "navigation@1"
},
{
"command": "chatgpt-for-vscode.whyBroken",
"group": "navigation@2"
},
{
"command": "chatgpt-for-vscode.optimizeCode",
"group": "navigation@3"
},
{
"command": "chatgpt-for-vscode.explainCode",
"group": "navigation@4"
},
{
"command": "chatgpt-for-vscode.refactor",
"group": "navigation@5"
},
{
"command": "chatgpt-for-vscode.setAPIKey",
"group": "navigation@6"
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "chatgpt-for-vscode",
"title": "ChatGPT",
"icon": "images/ChatGPT.png"
}
]
},
"views": {
"chatgpt-for-vscode": [
{
"type": "webview",
"id": "chatgpt-for-vscode.view",
"name": "ChatGPT"
}
]
}
},
-
commands:
command
: 命令Id,这个命令Id跟activationEvents
中配置的命令Id相同。title
:输入的命令的名称。Command + Shift + P
输入这个命令title后找到对应的命令。
-
menus:
editor/context
:配置编辑器右键展示内容。command
是命令Id,group
:右键后展示看板的命令位置。这里navigation
表示展示在模块的顶部。@*
表示排序。
-
viewsContainers
:activitybar
:配置右侧工具栏视图入口,配置后展示,注意这里的id
,要跟后面的
views
模块里面的视图key值保持一致,表示点击右侧icon
后展示那个视图,icon
是你本地的图片路径。
-
views
: 配置视图,这里使用webview
展示自定义视图
- 配置完成
package.json
后右键命令展示,左侧状态栏Icon,顶部命令行选择输入命令,已经可以展示了。运行npm run test
后会打开默认安装你插件的VSCode
面板,接下来就是完善触发命令后的代码逻辑了,核心在extension.ts
中实现。
extension.ts
模块开发
extension.ts
是程序的入口文件,里面有两个核心方法:
go
export function activate(context: vscode.ExtensionContext) {}
export function deactivate() {}
看字面意思很好理解,分别表示插件被激活与释放调用的生命周期方法.
1. 绑定APIKey
命令逻辑
要想使用OpenAI
的api,首先需要将自己的ApiKey
与插件进行关联。这里使用VSCode
自有APIvscode.window.showInputBox
来获取用户输入.
go
this.apiKey = await this.context.globalState.get('chatgpt-api-key');
if (!this.apiKey) {
const apiKeyInput = await vscode.window.showInputBox({
prompt: "请输入你的API Key",
ignoreFocusOut: true,
});
this.apiKey = apiKeyInput;
this.context.globalState.update('chatgpt-api-key', this.apiKey);
}
-
使用上下文的
globalState
来持久化保存ApiKey
-
如果要让这个命令生效,需要在
activate
中进行注册
go
context.subscriptions.push(vscode.commands.registerCommand('chatgpt-for-vscode.setAPIKey', resetToken))
async function resetToken() {
await vscode.window.showInputBox({
prompt: "请输入OpenAI API Key",
ignoreFocusOut: true,
});
}
执行
command + shift + p
输入命令titleGPT:绑定APIKey
后,展示效果如下:这样就完成了对用户
ApiKey
的绑定逻辑.
2. 命令触发逻辑
与绑定用户ApiKey
类似,其他命令的执行也是同样的流程,这里以onCommand:chatgpt-for-vscode.askGPT
命令来说明:
go
// 注册命令
vscode.commands.registerCommand('chatgpt-for-vscode.askGPT', askChatGPT)
// 命令实现
async function askChatGPT(userInput: string) {
let editor = vscode.window.activeTextEditor;
if (editor) {
const selectedCode = editor.document.getText(vscode.window.activeTextEditor?.selection);
if(selectedCode.length) {
chatViewProvider.sendOpenAiApiRequest(userInput, selectedCode);
vscode.window.showInformationMessage(selectedCode);
}else {
vscode.window.showInformationMessage(`请选中一段代码`);
}
}
}
-
注册命令后 使用
editor.document.getText(vscode.window.activeTextEditor?.selection)
来获取选中的代码段落,并判空. -
利用
chatViewProvider.sendOpenAiApiRequest(userInput, selectedCode);
利用这个方法用户输入的Prompt
与选中的代码端传递出去,这个方法的实现后面介绍,注册所有的命令后,
activate
方法是这样的
go
export function activate(context: vscode.ExtensionContext) {
const chatViewProvider = new view_provider.default(context);
context.subscriptions.push(
vscode.commands.registerCommand('chatgpt-for-vscode.askGPT', askChatGPT),
vscode.commands.registerCommand('chatgpt-for-vscode.whyBroken', askGPTWhyBroken),
vscode.commands.registerCommand('chatgpt-for-vscode.explainCode', askGPTToExplain),
vscode.commands.registerCommand('chatgpt-for-vscode.refactor', askGPTToRefactor),
vscode.commands.registerCommand('chatgpt-for-vscode.optimizeCode', askGPTToOptimize),
vscode.commands.registerCommand('chatgpt-for-vscode.setAPIKey', resetToken),
vscode.window.registerWebviewViewProvider("chatgpt-for-vscode.view", chatViewProvider, {
webviewOptions: { retainContextWhenHidden: true }})
);
async function askGPTWhyBroken() { await askChatGPT('说明下面的代码会出现什么问题?'); }
async function askGPTToExplain() { await askChatGPT('请帮我解释一下下面的代码?'); }
async function askGPTToRefactor() { await askChatGPT('帮我重构下面的代码'); }
async function askGPTToOptimize() { await askChatGPT('帮我优化下面的代码'); }
async function resetToken() {
await chatViewProvider.ensureApiKey();
}
async function askChatGPT(userInput: string) {
let editor = vscode.window.activeTextEditor;
if (editor) {
const selectedCode = editor.document.getText(vscode.window.activeTextEditor?.selection);
if(selectedCode.length) {
chatViewProvider.sendOpenAiApiRequest(userInput, selectedCode);
vscode.window.showInformationMessage(selectedCode);
}else {
vscode.window.showInformationMessage(`请选中一段代码`);
}
}
}
}
3.webView与chatViewProvider
上面的代码除了注册命令的APIregisterCommand
,还有一个注册自定义webview
视图的API,registerWebviewViewProvider
,作用是展示我们自定义的webview
,它有三个参数:
-
chatgpt-for-vscode.view
是视图Id,跟package.json
中views
模块对应的Id相同,表示为那个视图Id注册provider
. -
chatViewProvider
视图提供者. -
第三个参数:
webview
的属性配置,retainContextWhenHidden: true
表示:webview
被隐藏时保持状态,避免被重置.接下来重点来看
chatViewProvider
:作为自定义视图的
provider
首先需要继承vscode.WebviewViewProvider
这个接口
go
export interface WebviewViewProvider {
/**
* Revolves a webview view.
*
* `resolveWebviewView` is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*
* @param webviewView Webview view to restore. The provider should take ownership of this view. The
* provider must set the webview's `.html` and hook up all webview events it is interested in.
* @param context Additional metadata about the view being resolved.
* @param token Cancellation token indicating that the view being provided is no longer needed.
*
* @return Optional thenable indicating that the view has been fully resolved.
*/
resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Thenable<void> | void;
}
这个接口只有一个方法,resolveWebviewView
在视图首次可见时被调用。这可能发生在视图第一次加载时,或者当用户隐藏然后再次显示视图时。在这个方面里面设置webview
的html
与视图属性。
go
export default class ChatGptViewProvider implements vscode.WebviewViewProvider {
private webView?: vscode.WebviewView;
private apiKey?: string;
private message?: any;
constructor(private context: vscode.ExtensionContext) { }
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this.webView = webviewView;
// webview属性设置
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this.context.extensionUri]
};
// 返回Html代码
webviewView.webview.html = this.getHtml(webviewView.webview);
// 接收
webviewView.webview.onDidReceiveMessage(data => {
if (data.type === 'askChatGPT') {
this.sendOpenAiApiRequest(data.value);
}
});
if (this.message !== null) {
this.sendMessageToWebView(this.message);
this.message = null;
}
}
}
4. 通信机制
自定义的webview
和普通网页非常类似,都不能直接调用任何VSCodeAPI
,但是,它唯一特别之处就在于多了一个名叫acquireVsCodeApi
的方法,执行这个方法会返回一个超级阉割版的vscode
对象.利用这个对象,可以实现webview
与插件也就是provider
的通信。
provider
给webview
发送消息:
go
this.webView?.webview.postMessage(message);
webview
端接收消息:
go
window.addEventListener('message', event => {
const message = event.data;
console.log('Webview接收到的消息:', message);
}
webview
主动发送消息给provider
:
go
const vscode = acquireVsCodeApi();
vscode.postMessage({text: '你好,我是Webview啊!'});
provider
接收消息:
go
this.webView?.webview.onDidReceiveMessage(data => {
if (data.type === 'askChatGPT') {
this.sendOpenAiApiRequest(data.value);
}
});
了解完双方的通信机制后,基本逻辑是:当点击webview
上的发送
按钮后,将用户输入发送给ChatGPT
,ChatGPT
处理完成后将返回信息发送给webview
,webview
将回答信息展示出来,完成一次对话逻辑。
go
// 按钮绑定点击事件
document.getElementById("ask-button")?.addEventListener("click", submitHandler);
let submitHandler = function (e) {
e.preventDefault();
e.stopPropagation();
const input = document.getElementById("question-input");
if (input.value?.length > 0) {
// 发送消息给 插件,使其完成ChatGPT请求
vscode.postMessage({
type: "askChatGPT",
value: input.value,
});
input.value = "";
}
};
5. 调用OPenAI
接口
要想完成一次对话,需要调用OPenAI
的API.具体的API你可以在官网找到:
-
参数
model
是你要对话的ChatGPT
模型代码,不同模型针对同一个问题的答案会有所区别。具体模块区别可以参考下面图片:
更多模型可以点击这里去查看
-
参数
messages
: 你的问题信息 -
参数
temperature
: 它是一个用于控制生成文本的创造性的参数,其值介于0到2之间。值为1意味着模型将使用其默认采样策略,而值低于1.0将导致更保守和可预测的响应,值大于1.0将导致更有创造性和多样化的响应。 -
参数
max_tokens
: 生成对话的最大token
数量。这里的token
可以理解为模型的构建块。了解完成上面的参数,可以利用fetch
发起请求了:
go
let completion = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
body: JSON.stringify({
model: "text-davinci-003",
messages: [{ role: "user", content: question }],
temperature: 0.7
}),
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"Content-Type": 'application/json',
Authorization: 'Bearer ' + this.apiKey,
},
}) as any;
根据返回的数据结构,解析响应数据,并将结果发送给webview
进行展示,完成开发。
发布插件
-
扩展安装
通过以上步骤基本完成了插件的开发,接下来有两种方式发布我们的插件,如果你的插件只是在内网使用,可以通过命令:
vsce package
, 将插件打包为vsix
插件包,通过VSCode
的扩展,从VSIX
安装.当然首先要安装
vsce
这个工具
go
npm i vsce -g
-
上传到应用
VSCode
插件市场插件上传到
VSCode
应用市场,需要有应用市场的publisher
账号,具体的账号创建流程这里不再涉及,创建账号后,登录当前账号,执行vsce publish
,发布成功后大概需要过几分钟才能在应用市场搜到.发布账号有几个注意事项: -
README.md
文件默认会显示在插件主页; -
README.md
中的资源必须全部是HTTPS的,如果是HTTP会发布失败; -
CHANGELOG.md
会显示在变更选项卡; -
如果代码是放在
git
仓库并且设置了repository
字段,发布前必须先提交git
,否则会提示Git working directory not clean
-
发布后需要等待几分钟应用市场才会更新;
-
当然你可以在插件市场里面搜索
chatgpt-for-vscode
来试用这个插件;
总结
以上就是一个ChatGPT
插件的基本创建流程,核心是对VSCode API
以及ChatGPT API
的了解与使用。当然你所需要的功能都可以在对应的官方文档中找到。
参考文献:
https://code.visualstudio.com/api/extension-guides/overview
https://platform.openai.com/docs/api-reference/chat/create
http://blog.haoji.me/vscode-plugin-publish.html
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。