如何给自己写的vscode插件安排ai聊天机器人

想搞这个功能主要是分为两个部分,一个是ai的接口端。能实现输入,模型处理,返回新的输出。前端vscode如何承载这个ai聊天窗口,和后端ai会话进行联动。

1.前端

使用react的一个chatui的项目进行我们的主要渲染逻辑

这边使用chatui项目作为模板可以方便我们改造合适的聊天窗口。最终代码如下

app.tsx文件

typescript 复制代码
import Chat, { Bubble, useMessages, MessageProps } from "@chatui/core";
import axios from "axios";

const initialMessages = [
  {
    type: "text",
    content: { text: "主人好,我是您的贴心电影伴侣~" },
    user: {
      avatar: 'http://gw.alicdn.com/tfs/TB1U7FBiAT2gK0jSZPcXXcKkpXa-108-108.jpg',
    },
  },
];

interface QuickReplie {
  icon?: string;
  name: string;
  isNew?: boolean;
  isHighlight?: boolean;
}

// 默认快捷短语,可选
const defaultQuickReplies: Array<QuickReplie> = [
//   {
//     icon: "message",
//     name: "来一部喜剧电影",
//     isNew: true,
//     isHighlight: true,
//   },
//   {
//     name: "随便推荐一部电影",
//     isNew: true,
//   },
];

const App = () => {
  // 消息列表
  const { messages, appendMsg, setTyping } = useMessages(initialMessages);

  // 发送回调
  function handleSend(type: string, val: string) {
    if (type === "text" && val.trim()) {
      appendMsg({
        type: "text",
        content: { text: val },
        position: "right",
      });

      setTyping(true);

      axios
        .post("http://127.0.0.1:8802/ai/chat", {
          query:val
        })
        .then((res) => {
          const agentResponse = res.data;
          appendMsg({
            type: "text",
            content: { text: agentResponse },
          });
        })
        .catch(() => {
          appendMsg({
            type: "text",
            content: { text: "请求出了点问题,请重试~" },
          });
        });
    }
  }

  // 快捷短语回调,可根据 item 数据做出不同的操作,这里以发送文本消息为例
  function handleQuickReplyClick(item: QuickReplie) {
    handleSend("text", item.name);
  }
  function renderNavbar() {
    return "";
  }
  function renderMessageContent(msg: MessageProps) {
    const { type, content } = msg;

    // 根据消息类型来渲染
    switch (type) {
      case "text":
        return <Bubble content={content.text} />;
      case "image":
        return (
          <Bubble type="image">
            <img src={content.picUrl} alt="" />
          </Bubble>
        );
      default:
        return null;
    }
  }

  return (
    <Chat
    renderNavbar={renderNavbar}
      messages={messages}
      renderMessageContent={renderMessageContent}
      quickReplies={defaultQuickReplies}
      onQuickReplyClick={handleQuickReplyClick}
      onSend={handleSend}
    />
  );
};

export default App;

2.main.tsx样式自定义主题

可以从官网的页面进行定制

ChatUI Theme Builder

2.vscode端

把上一个步骤打包的dist内容移动到view2下面。

extension.ts入口文件

新建按钮

ini 复制代码
   // 显示按钮
        const myButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
        myButton.tooltip = "Open Chat";
        myButton.text = `$(comment-discussion)`
        myButton.color = 'white';
        myButton.command = 'autopub.chatPanel';
        myButton.show();

$(comment-discussion) 这个是使用vscode的一个默认icon库,这个库被vscode集成了,可以直接用。

更多icon参考 (microsoft.github.io/vscode-codi...)

面板创建

dart 复制代码
let panel: vscode.WebviewPanel

        let chatPanel = vscode.commands.registerCommand('autopub.chatPanel', async () => {
            // 创建一个新的 WebView 视图
            panel = vscode.window.createWebviewPanel('myChatWebView', '电影助手',
                vscode.ViewColumn.One,
                {
                    enableScripts: true,
                    localResourceRoots: [
                        vscode.Uri.file(path.join(context.extensionPath, 'out/view2')),
                    ],
                }
            );

            // 注册消息监听器来接收来自webview的消息
            panel.webview.onDidReceiveMessage(async event => {
                console.log(event);
            });

            // 在 WebView 视图中加载 HTML 内容
            panel.webview.html = await getWebviewHTML(panel, extensionUri ?? Uri.file(''));

            // webview销毁函数
            panel.onDidDispose(() => {
                console.log('panel onDidDispose')
            })

        })
        context.subscriptions.push(chatPanel);

动态引入webview

ini 复制代码
import * as vscode from 'vscode';
import * as path from 'path';
import { Uri } from 'vscode';

module.exports = class ChatWebview {
    async registe(context: vscode.ExtensionContext) {
        // 显示按钮
        const myButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
        myButton.tooltip = "Open Chat";
        myButton.text = `$(comment-discussion)`
        myButton.color = 'white';
        myButton.command = 'autopub.chatPanel';
        myButton.show();
        let extensionUri = vscode.extensions.getExtension('larry.autopub')?.extensionUri;
        let panel: vscode.WebviewPanel

        let chatPanel = vscode.commands.registerCommand('autopub.chatPanel', async () => {
            // 创建一个新的 WebView 视图
            panel = vscode.window.createWebviewPanel('myChatWebView', '电影助手',
                vscode.ViewColumn.One,
                {
                    enableScripts: true,
                    localResourceRoots: [
                        vscode.Uri.file(path.join(context.extensionPath, 'out/view2')),
                    ],
                }
            );

            // 注册消息监听器来接收来自webview的消息
            panel.webview.onDidReceiveMessage(async event => {
                console.log(event);
            });

            // 在 WebView 视图中加载 HTML 内容
            panel.webview.html = await getWebviewHTML(panel, extensionUri ?? Uri.file(''));

            // webview销毁函数
            panel.onDidDispose(() => {
                console.log('panel onDidDispose')
            })

        })
        context.subscriptions.push(chatPanel);


    }
    deactivate() {
    }
}
// 获取目录下文件信息
async function listFilesInDirectory(uri: vscode.Uri): Promise<any> {
    try {
        // 使用vscode.workspace.fs.readDirectory来获取目录内容
        const entries = await vscode.workspace.fs.readDirectory(uri) as any;

        let map = {} as any
        entries.map((entry: string[]) => {
            if (entry[0].includes('css')) {
                map['css'] = vscode.Uri.joinPath(uri, entry[0])
            }
            if (entry[0].includes('js')) {
                map['js'] = vscode.Uri.joinPath(uri, entry[0])
            }
        });

        return map;
    } catch (error) {
        console.error(`Failed to read directory: ${error}`);
        return [];
    }
}

async function getWebviewHTML(panel: vscode.WebviewPanel, extensionUri: Uri): Promise<string> {
    let res = await listFilesInDirectory(vscode.Uri.joinPath(extensionUri, 'out', 'view2', 'assets'))
    // js地址动态转换
    const scriptUri = panel.webview.asWebviewUri(res['js']);
    // css地址动态转换
    const styleUri = panel.webview.asWebviewUri(res['css']);

    return `<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <base target="_top" href="/"/>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <script type="module" crossorigin src="${scriptUri}"></script>
      <link rel="stylesheet" href="${styleUri}">
    </head>
    <body>
      <div id="root"></div>
      
    </body>
    <style>
        .Navbar {
            opacity:0!important;
            margin-top:-20px;
        }
    </style>
  </html>
  `;
}

核心逻辑就是使用读取文件目录下两个核心的打包文件。然后处理为vscode的路径即可

3.后端

后端是关于接口的对接,这块就使用ai来玩一玩了。

新建springboot项目

注册和使用阿里的免费模型

bailian.console.aliyun.com/

在yml文件配置模型的访问key

ini 复制代码
spring.ai.dashscope.api-key=sk-7b4e30c2a85xxxx

依赖导入

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
    <version>1.0.0-M3.1</version>
</dependency>
<repositories>
  <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/milestone</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
</repositories>

编写rest接口

less 复制代码
@RestController
@RequestMapping("/ai")
@CrossOrigin(origins = "*")  // 允许所有来源跨域访问
public class ChatController {
    private final ChatClient chatClient;
    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @PostMapping("/chat")
    public String chat(@RequestBody AskInfo askInfo) {
        return this.chatClient.prompt()
                .user(askInfo.getQuery())
                .call()
                .content();
    }
}

4.最终效果

通过点击按钮,打开我们的聊天窗口,进行问答。

免费的比较卡啊,相关的代码都放在了github上面。有想要的可以自取。

github.com/MrYZhou/vs-...

更多

下一篇准备写vscode插件开发中如何使用多模块构建,目前还有些细节待我优化下。。然后把问答模型添加上知识库的方式。

相关推荐
涔溪6 分钟前
云原生后端深度解析
后端·云原生
祁思妙想7 分钟前
《JavaEE进阶》----21.<基于Spring图书管理系统②(图书列表+删除图书+更改图书)>
java·后端·spring
喝旺仔la11 分钟前
异步提交Django
后端·python·django
孤客网络科技工作室14 分钟前
Python Web 应用开发基础知识
开发语言·前端·python
枫叶_v14 分钟前
【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)
spring boot·后端·mysql
摸鲨鱼的脚18 分钟前
vue动态列(表头)
前端·javascript·vue.js
激流丶20 分钟前
【缓存策略】你知道 Write Through(直写)这个缓存策略吗?
java·分布式·后端·缓存·中间件
羽星_s23 分钟前
Dial-insight:利用高质量特定领域数据微调大型语言模型防止灾难性遗忘
人工智能·深度学习·语言模型·自然语言处理
爱健身的程序员23 分钟前
鸿蒙应用开发--状态管理
前端·harmonyos
sp_fyf_202424 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-03
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘