想搞这个功能主要是分为两个部分,一个是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样式自定义主题
可以从官网的页面进行定制
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项目
注册和使用阿里的免费模型
在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上面。有想要的可以自取。
更多
下一篇准备写vscode插件开发中如何使用多模块构建,目前还有些细节待我优化下。。然后把问答模型添加上知识库的方式。