当前位置: 首页 > news >正文

vscode插件开发-创建AI聊天面板

这篇给大家说说如何在vscode创建一个和AI聊天的面板,参考文章vscode插件官网Webview API |Visual Studio Code 扩展 应用程序接口

vscode有内置的webview API,所以允许扩展在 Visual Studio Code 中创建完全可自定义的视图

我们在项目src目录下新建一个,chatPanel.ts文件

然后我们需要实现一个类,在里面编写一些必要的代码来显示打开聊天面板

下面我具体说说几个功能 

一.创建面板与显示

代码有注释就不详细说了,其实就是调用vscode的API

  public static createOrShow(extensionUri: vscode.Uri) {const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;// 如果已经存在面板,则显示它。if (ChatPanel.currentPanel) {ChatPanel.currentPanel._panel.reveal(column);return;}// 否则,创建一个新的面板。const panel = vscode.window.createWebviewPanel(ChatPanel.viewType,'AI Chat',vscode.ViewColumn.Three,{enableScripts: true,localResourceRoots: [extensionUri],// 可以设置面板的初始大小和位置retainContextWhenHidden: true  // 保持上下文,避免重新加载});ChatPanel.currentPanel = new ChatPanel(panel, extensionUri);}

二.自定义面板内容

创建好面板之后,我们需要写一个函数去返回想展示的html

 private _getHtmlForWebview(webview: vscode.Webview) {const nonce = getNonce();const cspSource = webview.cspSource;return `<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${cspSource} https:; script-src 'nonce-${nonce}'; style-src 'unsafe-inline' ${cspSource};"><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>AI Chat</title><style>body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', Roboto, 'Helvetica Neue', Arial; margin:0; padding:0; height:100vh; display:flex; flex-direction:column;/* 设置最大宽度,让界面不会太宽 */max-width: 800px; margin: 0 auto;}#messages { flex:1; padding:12px; overflow:auto; background:#f6f8fa;/* 限制消息区域的最大宽度 */max-width: 100%;}.msg { margin:8px 0; padding:8px 12px; border-radius:8px; /* 调整消息最大宽度,让界面更紧凑 */max-width:70%; word-wrap: break-word;}.user { background:#0066ff; color:#fff; margin-left:auto }.assistant { background:#e5e7eb; color:#111; margin-right:auto }#composer { display:flex; padding:8px; border-top:1px solid #ddd;/* 限制输入框区域宽度 */max-width: 100%;}#input { flex:1; padding:8px; font-size:14px; max-width: 100%; }button { margin-left:8px; padding:8px 12px }</style></head><body><div id="messages"></div><form id="composer"><input id="input" autocomplete="off" placeholder="输入消息并回车或点击发送..." /><button type="submit">发送</button></form><script nonce="${nonce}">const vscode = acquireVsCodeApi();const messagesEl = document.getElementById('messages');const inputEl = document.getElementById('input');function appendMessage(text, cls) {const div = document.createElement('div');div.className = 'msg ' + cls;div.textContent = text;messagesEl.appendChild(div);messagesEl.scrollTop = messagesEl.scrollHeight;}document.getElementById('composer').addEventListener('submit', (e) => {e.preventDefault();const text = inputEl.value.trim();if (!text) return;appendMessage(text, 'user');vscode.postMessage({ type: 'userMessage', text });inputEl.value = '';inputEl.focus();});// 接收来自扩展的消息window.addEventListener('message', event => {const msg = event.data;if (msg.type === 'assistantMessage') {appendMessage(msg.text, 'assistant');}});</script></body></html>`;}

面板有了,里面的内容也可以自定义,但是还有很重要的一个点,其实很多时候,我们是需要Webview与拓展主进程去通信而实现一些功能的

三.拓展与面板的通信

 (一).拓展主进程给Webview发消息   

VS Code 插件中,扩展主进程(Node.js 环境)与 Webview(浏览器环境)的通信是通过「消息传递」完成的,本质是 JSON 数据的双向传递。这段代码展示了「主进程主动发送消息,Webview 接收处理」的单向流程,具体分三步:

1. 主进程:注册发送消息的命令

在扩展激活函数(activate)中,注册了一个名为 catCoding.doRefactor 的命令,作用是给已创建的 Webview 面板发送消息:

// 注册发送消息的命令
vscode.commands.registerCommand('catCoding.doRefactor', () => {if (!currentPanel) return; // 确保面板已存在// 发送消息:JSON 格式,可自定义字段(这里用 command 标识操作)currentPanel.webview.postMessage({ command: 'refactor' });
});
  • 关键 API:webview.postMessage(data)data 必须是 JSON 可序列化的数据(字符串、数字、对象等)。
  • 作用:当用户触发 catCoding.doRefactor 命令(比如通过快捷键或命令面板),主进程就会给 Webview 发一条 { command: 'refactor' } 的消息。
2. Webview:监听并接收消息

在 Webview 的 HTML 中,通过 JavaScript 监听 message 事件,接收主进程发来的消息:

<script>// Webview 中监听消息事件window.addEventListener('message', event => {const message = event.data; // 解析主进程发来的 JSON 数据// 根据消息内容处理逻辑switch (message.command) {case 'refactor':// 这里是自定义处理:console.log('hello')break;}});
</script>

(二).Webview 给拓展主进程发消息

Webview(浏览器环境)向主进程(Node.js 环境)发送消息,本质是通过 VS Code 提供的 API 把数据从前端环境传递到后端扩展,流程分两步:

1. Webview 中:发送消息

在 Webview 的 HTML/JS 中,通过 acquireVsCodeApi() 获取 VS Code 提供的内置 API 对象,然后调用其 postMessage 方法发送消息(消息需是 JSON 可序列化数据)。

<script>// 获取 VS Code 提供的 API 对象(仅在 Webview 中可用)const vscode = acquireVsCodeApi();// 假设用户点击了一个按钮,触发发送消息document.getElementById('sendBtn').addEventListener('click', () => {// 发送消息:可以是对象、字符串、数字等vscode.postMessage({command: 'save',data: { content: 'Hello from Webview', timestamp: Date.now() }});});
</script>
  • 关键:acquireVsCodeApi() 是 VS Code 注入到 Webview 中的全局方法,返回的 vscode 对象提供了 postMessage 方法,专门用于向主进程发送消息。
  • 消息格式:与主进程发消息一致,需是 JSON 可序列化数据(避免函数、DOM 等无法序列化的类型)。
2. 主进程中:监听消息

在扩展的激活函数(activate)中,通过 Webview 实例的 onDidReceiveMessage 事件监听 Webview 发来的消息,并定义处理逻辑。

export function activate(context: vscode.ExtensionContext) {// 创建 Webview 面板(省略部分代码)const panel = vscode.window.createWebviewPanel(...);// 监听 Webview 发来的消息panel.webview.onDidReceiveMessage((message) => { // message 就是 Webview 发送的 JSON 数据switch (message.command) {case 'save':// 处理 Webview 发来的 "保存" 命令console.log('收到 Webview 消息:', message.data);// 可以调用 VS Code API 执行操作(如写入文件、显示提示)vscode.window.showInformationMessage(`已保存:${message.data.content}`);break;}},undefined, // 错误处理(可选)context.subscriptions // 加入订阅,确保扩展卸载时自动清理);
}
  • 关键 API:webview.onDidReceiveMessage(callback)callback 的参数就是 Webview 发送的消息数据。
  • 生命周期管理:将事件监听加入 context.subscriptions,能确保扩展卸载时自动取消监听,避免内存泄漏。
双向通信的完整闭环

结合之前主进程给 Webview 发消息的逻辑,整个通信闭环是:

  1. 主进程 → Webview:currentPanel.webview.postMessage(...) 发送,Webview 用 window.addEventListener('message') 接收。
  2. Webview → 主进程:Webview 用 vscode.postMessage(...) 发送,主进程用 webview.onDidReceiveMessage(...) 接收。

最重要的几个点已经说明了下面是chatPanel.ts的完整代码

import * as vscode from 'vscode';export class ChatPanel implements vscode.Disposable {public static currentPanel: ChatPanel | undefined;public static readonly viewType = 'aiChat.panel';private readonly _panel: vscode.WebviewPanel;private readonly _extensionUri: vscode.Uri;private _disposables: vscode.Disposable[] = [];public static createOrShow(extensionUri: vscode.Uri) {const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;// 如果已经存在面板,则显示它。if (ChatPanel.currentPanel) {ChatPanel.currentPanel._panel.reveal(column);return;}// 否则,创建一个新的面板。const panel = vscode.window.createWebviewPanel(ChatPanel.viewType,'AI Chat',vscode.ViewColumn.Three,{enableScripts: true,localResourceRoots: [extensionUri],// 可以设置面板的初始大小和位置retainContextWhenHidden: true  // 保持上下文,避免重新加载});ChatPanel.currentPanel = new ChatPanel(panel, extensionUri);}private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {this._panel = panel;this._extensionUri = extensionUri;// 设置 webview 的初始 HTML 内容this._panel.webview.html = this._getHtmlForWebview(this._panel.webview);// 监听来自 webview 的消息this._panel.webview.onDidReceiveMessage(message => {switch (message.type) {case 'userMessage':this._handleUserMessage(message.text);return;}},null,this._disposables);this._panel.onDidDispose(() => this.dispose(), null, this._disposables);}public dispose() {ChatPanel.currentPanel = undefined;// 清理资源this._panel.dispose();while (this._disposables.length) {const d = this._disposables.pop();if (d) {d.dispose();}}}private _handleUserMessage(text: string) {// 简单模拟 AI 响应:异步延迟后返回一条回复const reply = `模拟回复:我收到了你的消息 — "${text}"`;// 模拟延迟(例如调用远端 AI API 的占位)setTimeout(() => {this._panel.webview.postMessage({ type: 'assistantMessage', text: reply });}, 700);}private _getHtmlForWebview(webview: vscode.Webview) {const nonce = getNonce();const cspSource = webview.cspSource;return `<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${cspSource} https:; script-src 'nonce-${nonce}'; style-src 'unsafe-inline' ${cspSource};"><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>AI Chat</title><style>body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', Roboto, 'Helvetica Neue', Arial; margin:0; padding:0; height:100vh; display:flex; flex-direction:column;/* 设置最大宽度,让界面不会太宽 */max-width: 800px; margin: 0 auto;}#messages { flex:1; padding:12px; overflow:auto; background:#f6f8fa;/* 限制消息区域的最大宽度 */max-width: 100%;}.msg { margin:8px 0; padding:8px 12px; border-radius:8px; /* 调整消息最大宽度,让界面更紧凑 */max-width:70%; word-wrap: break-word;}.user { background:#0066ff; color:#fff; margin-left:auto }.assistant { background:#e5e7eb; color:#111; margin-right:auto }#composer { display:flex; padding:8px; border-top:1px solid #ddd;/* 限制输入框区域宽度 */max-width: 100%;}#input { flex:1; padding:8px; font-size:14px; max-width: 100%; }button { margin-left:8px; padding:8px 12px }</style></head><body><div id="messages"></div><form id="composer"><input id="input" autocomplete="off" placeholder="输入消息并回车或点击发送..." /><button type="submit">发送</button></form><script nonce="${nonce}">const vscode = acquireVsCodeApi();const messagesEl = document.getElementById('messages');const inputEl = document.getElementById('input');function appendMessage(text, cls) {const div = document.createElement('div');div.className = 'msg ' + cls;div.textContent = text;messagesEl.appendChild(div);messagesEl.scrollTop = messagesEl.scrollHeight;}document.getElementById('composer').addEventListener('submit', (e) => {e.preventDefault();const text = inputEl.value.trim();if (!text) return;appendMessage(text, 'user');vscode.postMessage({ type: 'userMessage', text });inputEl.value = '';inputEl.focus();});// 接收来自扩展的消息window.addEventListener('message', event => {const msg = event.data;if (msg.type === 'assistantMessage') {appendMessage(msg.text, 'assistant');}});</script></body></html>`;}
}function getNonce() {let text = '';const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';for (let i = 0; i < 32; i++) {text += possible.charAt(Math.floor(Math.random() * possible.length));}return text;
}

我们在主拓展文件extension.ts去处理他,把他加入到activate函数里面

import { ChatPanel } from './chatPanel';const openChatDisposable = vscode.commands.registerCommand('hello.openChat', () => {ChatPanel.createOrShow(context.extensionUri);
});context.subscriptions.push(openChatDisposable);

实现效果:

这个界面可以继续美化,但是篇幅过长,就不继续说明了,下一篇会讲,如何接入deepseek免费大模型,实现简单的对话!!!

http://www.dtcms.com/a/555017.html

相关文章:

  • 广州行业门户网站建设怎样做网站运营
  • 东莞做网站公司电话wordpress多说加载慢
  • wordpress网站破解仿京东网站后台
  • 做网站建设的基本步骤趣闻网站如何做
  • 网站制作属于什么行业我国的跨境电商平台有哪些
  • 提高网站规范化建设帮忙做ppt赚钱的网站
  • JAVA1031 NUM求和
  • 2021年免费的网站有哪些网站被降权怎么办
  • 基于协同过滤算法的话剧购票系统(论文+源码)
  • 正规网站建设哪家好安徽省水利建设厅官方网站
  • zencart外贸建站网站建设联
  • 用 Python 实现连续数据分组求和并回写
  • 从0学Java--day7
  • 做ppt医学专业图片网站徐州哪里做网站
  • 容器之间怎么通信?Docker 网络全解析
  • 网站优化平台网站建设 岗位
  • 老干部活动中心网站建设方案wordpress 企业库插件
  • 网站前台和后台轻松seo优化排名
  • 怎样防止网站被黑专业做网站制作自助建站系统
  • 了解学习LVS-DR模式配置
  • 对网站建设安全性的要求网站的建设成本
  • 中国七大城市电动汽车使用与充电分析数据集
  • 博爱网站建设重庆响应式网站方案
  • 微前端乾坤vue3项目使用tinymce,通过npm,yarn,pnpm包安装成功,但是引用报错无法使用
  • 石家庄房产信息网查询系统googleseo优化
  • Spec-kit 入门
  • 做影视网站代理犯法吗外贸做的社交网站
  • 服装工厂做网站的好处电子商务概念
  • 第三篇:不靠毅力靠方法:“小步子原理”启动改变飞轮
  • 网站图片太多做外贸需要浏览外国网站