import vscode from "vscode"; import path from "path"; import {callQwenAPI} from "./utils/modelApi"; import {generateCodeDiff, getFileExtension} from "./utils/common"; // 定义全局变量来存储面板状态 let currentSessionHistory: { role: string; content: string }[] = []; // 存储工作区变更的文件,包含状态信息 let workspaceChanges: { [key: string]: { original: string; modified: string; status: 'pending' | 'accepted' | 'rejected' } } = {}; export class MessageHandler { private provider: any; // 这里应该使用AISidebarViewProvider类型,但由于循环依赖问题,暂时使用any private context: vscode.ExtensionContext; private currentReader: ReadableStreamDefaultReader | null = null; // 添加当前reader引用 constructor(provider: any, context: vscode.ExtensionContext) { this.provider = provider; this.context = context; this.currentReader = null; } public async handleMessage(message: any) { switch (message.command) { case 'ask': await this.handleAsk(message); break; case 'cancelRequest': await this.handleCancelRequest(); break; case 'selectContextFile': await this.handleSelectContextFile(); break; case 'getFileList': await this.handleGetFileList(); break; case 'selectFileByPath': await this.handleSelectFileByPath(message); break; case 'showCodeDiffInEditor': await this.handleShowCodeDiffInEditor(message); break; case 'acceptChanges': await this.handleAcceptChanges(message); break; case 'rejectChanges': await this.handleRejectChanges(message); break; case 'openWorkspaceFile': await this.handleOpenWorkspaceFile(message); break; case 'getCodeChanges': this.handleGetCodeChanges(); break; case 'createNewFile': await this.handleCreateNewFile(message); break; case 'restoreState': this.handleRestoreState(message); break; } } private async handleAsk(message: any) { const question = message.text; const fileContent = message.fileContent; const fileName = message.fileContentPath ? path.basename(message.fileContentPath) : "current_file.txt"; const history = currentSessionHistory; currentSessionHistory = [...history, {role: 'user', content: question}]; this.provider._postMessage({command: 'addMessage', role: 'user', content: question}); try { const response = await callQwenAPI(question, history, fileContent, this.context, fileName); // 发送开始流式传输的消息 this.provider._postMessage({command: 'startStream', role: 'assistant'}); // 处理流式响应 const reader = response.body?.getReader(); this.currentReader = reader; // 保存reader引用 const decoder = new TextDecoder('utf-8'); let aiMessage = ''; if (reader) { try { while (true) { const {done, value} = await reader.read(); if (done) break; const chunk = decoder.decode(value, {stream: true}); // 检查是否包含event: end事件 let processedChunk = chunk; if (chunk.includes('event: end')) { // 只处理event: end之前的内容 const endIndex = chunk.indexOf('event: end'); processedChunk = chunk.substring(0, endIndex); // 处理截取后的内容 if (processedChunk.startsWith('data:')) { // 移除data:前缀 processedChunk = processedChunk.replace(/^data:/, ''); // 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的 processedChunk = processedChunk.replace(/^(\n{2,})/, (match) => { // 删除所有连续的换行符 return ''; }); } if (processedChunk) { aiMessage += processedChunk; // 发送流式数据到前端 this.provider._postMessage({ command: 'streamData', data: processedChunk }); } // 停止继续处理 break; } if (chunk.startsWith('data:')) { // 移除data:前缀 processedChunk = chunk.replace(/^data:/, ''); // 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的 processedChunk = processedChunk.replace(/^(\n{2,})/, (match) => { // 删除所有连续的换行符 return ''; }); } aiMessage += processedChunk; // 发送流式数据到前端 this.provider._postMessage({ command: 'streamData', data: processedChunk }); } } catch (readError: any) { // 检查是否是由于取消请求导致的中断 if (readError.name === 'AbortError' || readError.message.includes('cancel')) { console.log('请求已被用户中断'); // 通知前端请求已中断 this.provider._postMessage({command: 'requestCancelled'}); // 重置状态但不抛出错误 this.provider._postMessage({command: 'hideLoading'}); return; } // 如果是其他错误,重新抛出 throw readError; } finally { // 清理reader引用 this.currentReader = null; } // 流传输完成后,更新会话历史 currentSessionHistory = [...currentSessionHistory, {role: 'assistant', content: aiMessage}]; // 解析AI响应中的多个代码块并生成独立的codeDiff信息 const codeBlocks = aiMessage.match(/```[\s\S]*?```/g) || []; // 存储所有生成的codeDiff const allCodeDiffs = []; for (const block of codeBlocks) { // 生成codeDiff const codeDiff = generateCodeDiff(fileContent, block); if (codeDiff) { // @ts-ignore allCodeDiffs.push(codeDiff); } } console.log("allCodeDiffs:",allCodeDiffs) // 通知前端流传输完成 this.provider._postMessage({ command: 'endStream', content: aiMessage, codeDiffs: allCodeDiffs // 传递多个codeDiff信息 }); // 如果有代码差异,保存到工作区变更中 if (allCodeDiffs.length > 0 && fileContent && message.fileContentPath) { // 为每个codeDiff创建独立的workspaceChange for (let i = 0; i < allCodeDiffs.length; i++) { const codeDiff = allCodeDiffs[i]; const filePath = message.fileContentPath; // 创建唯一的工作区变更键 const changeKey = `${filePath}`; workspaceChanges[changeKey] = { original: fileContent, // @ts-ignore modified: codeDiff.modifiedCode, status: 'pending' }; } // 通知 WebView 更新工作区文件列表 this.provider._postMessage({ command: 'updateWorkspaceFiles', files: workspaceChanges }); } } } catch (error: any) { // 检查是否是由于取消请求导致的中断 if (error.name === 'AbortError' || error.message.includes('cancel') || error.message.includes('中断')) { console.log('请求已被用户中断'); this.provider._postMessage({command: 'requestCancelled'}); } else { this.provider._postMessage({command: 'addMessage', role: 'ai', content: '调用失败:' + error.message}); } } this.provider._postMessage({command: 'hideLoading'}); } private async handleSelectContextFile() { // 执行文件选择逻辑 const uris = await vscode.window.showOpenDialog({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, title: "选择一个文件作为上下文" }); if (uris && uris.length > 0) { const selectedFileUri = uris[0]; const fileContent = await vscode.workspace.fs.readFile(selectedFileUri); const decodedContent = new TextDecoder("utf-8").decode(fileContent); this.provider._postMessage({ command: 'setContextFile', fileName: selectedFileUri.fsPath, fileContent: decodedContent }); } } private async handleGetFileList() { // 执行获取文件列表逻辑 const workspaceFolders = vscode.workspace.workspaceFolders; if (workspaceFolders && workspaceFolders.length > 0) { const fileUris = await vscode.workspace.findFiles('**/*', '**/node_modules/**'); const files = fileUris.map(uri => { const relativePath = vscode.workspace.asRelativePath(uri); return { path: uri.fsPath, relativePath: relativePath, name: path.basename(uri.fsPath), isDirectory: false // 简化处理,实际可以根据扩展名判断 }; }); this.provider._postMessage({ command: 'fileList', files: files }); } } private async handleSelectFileByPath(message: any) { // 根据路径选择文件 try { const fileUri = vscode.Uri.file(message.filePath); const fileContent = await vscode.workspace.fs.readFile(fileUri); const decodedContent = new TextDecoder("utf-8").decode(fileContent); this.provider._postMessage({ command: 'setContextFile', fileName: fileUri.fsPath, fileContent: decodedContent }); } catch (error: any) { vscode.window.showErrorMessage(`无法读取文件: ${error.message}`); } } private async handleShowCodeDiffInEditor(message: any) { // 在编辑器中显示代码差异 try { const originalCode = message.originalCode || ''; const modifiedCode = message.modifiedCode || ''; // 创建原始代码的虚拟文档 const originalUri = vscode.Uri.parse('untitled:original-code.txt'); const originalDoc = await vscode.workspace.openTextDocument(originalUri); const originalEditor = await vscode.window.showTextDocument(originalDoc, vscode.ViewColumn.One, true); await originalEditor.edit(edit => { edit.insert(new vscode.Position(0, 0), originalCode); }); // 创建修改后代码的虚拟文档 const modifiedUri = vscode.Uri.parse('untitled:modified-code.txt'); const modifiedDoc = await vscode.workspace.openTextDocument(modifiedUri); const modifiedEditor = await vscode.window.showTextDocument(modifiedDoc, vscode.ViewColumn.Two, true); await modifiedEditor.edit(edit => { edit.insert(new vscode.Position(0, 0), modifiedCode); }); // 使用 VS Code 的 diff 命令显示差异 await vscode.commands.executeCommand( 'vscode.diff', originalUri, modifiedUri, 'Code Diff - Original ↔ Modified' ); } catch (error: any) { vscode.window.showErrorMessage(`显示代码差异失败: ${error.message}`); } } private async handleAcceptChanges(message: any) { // 接受代码变更 try { const {filePath, modifiedContent} = message; const fileUri = vscode.Uri.file(filePath); // 将修改后的内容写入文件 const encodedContent = new TextEncoder().encode(modifiedContent); await vscode.workspace.fs.writeFile(fileUri, encodedContent); // 更新工作区变更状态 if (workspaceChanges[filePath]) { workspaceChanges[filePath].status = 'accepted'; } // 通知 WebView 更新工作区文件列表 this.provider._postMessage({ command: 'updateWorkspaceFiles', files: workspaceChanges }); vscode.window.showInformationMessage('代码变更已应用'); } catch (error: any) { vscode.window.showErrorMessage(`应用代码变更失败: ${error.message}`); } } private async handleRejectChanges(message: any) { // 拒绝代码变更 try { const {filePath} = message; // 更新工作区变更状态 if (workspaceChanges[filePath]) { workspaceChanges[filePath].status = 'rejected'; } // 通知 WebView 更新工作区文件列表 this.provider._postMessage({ command: 'updateWorkspaceFiles', files: workspaceChanges }); vscode.window.showInformationMessage('代码变更已拒绝'); } catch (error: any) { vscode.window.showErrorMessage(`拒绝代码变更失败: ${error.message}`); } } private async handleOpenWorkspaceFile(message: any) { // 打开工作区文件 try { const {filePath} = message; const fileUri = vscode.Uri.file(filePath); // 检查文件是否已经打开 let fileAlreadyOpen = false; const openedTextDocuments = vscode.workspace.textDocuments; for (const doc of openedTextDocuments) { if (doc.uri.fsPath === fileUri.fsPath) { fileAlreadyOpen = true; // 激活已打开的文件 await vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.One // 切换到已打开的文件 }); break; } } // 如果文件未打开,则在新窗口打开 if (!fileAlreadyOpen) { await vscode.window.showTextDocument(fileUri, { viewColumn: vscode.ViewColumn.One // 在第一列打开文件 }); } } catch (error: any) { vscode.window.showErrorMessage(`打开文件失败: ${error.message}`); } } private handleGetCodeChanges() { // 获取代码变更 this.provider._postMessage({ command: 'codeChanges', changes: workspaceChanges }); } private async handleCreateNewFile(message: any) { // 创建新文件功能 try { const {fileName, content, language} = message; // 获取根工作区路径 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { vscode.window.showErrorMessage('未找到工作区'); return; } // 根据语言确定文件扩展名 const fileExtension = getFileExtension(language); const fullFileName = fileName.endsWith(fileExtension) ? fileName : `${fileName}${fileExtension}`; // 创建文件路径(在根目录下) const filePath = path.join(workspaceFolders[0].uri.fsPath, fullFileName); const fileUri = vscode.Uri.file(filePath); // 将内容写入文件 const encodedContent = new TextEncoder().encode(content); await vscode.workspace.fs.writeFile(fileUri, encodedContent); // 在编辑器中打开新创建的文件 await vscode.window.showTextDocument(fileUri, { viewColumn: vscode.ViewColumn.One }); vscode.window.showInformationMessage(`文件 ${fullFileName} 已创建`); } catch (error: any) { vscode.window.showErrorMessage(`生成代码文件失败: ${error.message}`); } } private handleRestoreState(message: any) { // 恢复面板状态 currentSessionHistory = message.history || []; // 恢复显示历史消息 currentSessionHistory.forEach(msg => { this.provider._postMessage({ command: 'addMessage', role: msg.role, content: msg.content, codeDiff: null }); }); // 恢复工作区变更 if (message.workspaceChanges) { this.provider._postMessage({ command: 'updateWorkspaceFiles', files: message.workspaceChanges }); } } // 添加处理取消请求的方法 private async handleCancelRequest() { console.log('收到取消请求'); if (this.currentReader) { try { await this.currentReader.cancel(); this.currentReader = null; console.log('请求已取消'); } catch (error) { console.error('取消请求时出错:', error); } } } }