let selectedCodeForContext = ''; let contextFilePath = ''; // 存储当前上下文文件路径 let streamingMessageElement = null; // 用于存储当前流式消息的DOM元素 let streamingMessageContent = ''; // 用于存储当前流式消息的内容 let currentReader = null; // 用于存储当前流的reader,以便可以取消 let isRequestInProgress = false; // 标记是否有请求正在进行 let currentSessionHistory = []; // 存储当前会话历史 const vscode = acquireVsCodeApi(); document.addEventListener('DOMContentLoaded', () => { const chatForm = document.getElementById('chat-form'); const userInput = document.getElementById('user-input'); const chatBox = document.getElementById('chat-box'); const loading = document.getElementById('loading'); const sendBtn = document.getElementById('send-btn'); // 初始化显示工作区容器 const workspaceContainer = document.querySelector('.workspace-container'); if (workspaceContainer) { workspaceContainer.style.display = 'none'; } chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const text = userInput.value.trim(); // 如果输入为空,不执行任何操作 if (!text && !isRequestInProgress) return; // 如果有请求正在进行,点击按钮将中断请求 if (isRequestInProgress) { logToExtension("取消请求"); // 发送取消请求的消息到扩展 vscode.postMessage({ command: 'cancelRequest' }); // 重置按钮状态 resetSendButton(); hideLoading(); return; } // 发送消息 vscode.postMessage({ command: 'ask', text, fileContent: selectedCodeForContext, fileContentPath: contextFilePath // 添加文件路径 }); // 切换按钮为停止按钮 isRequestInProgress = true; if (sendBtn) { sendBtn.textContent = '停止'; sendBtn.classList.add('stop-button'); } showLoading(); userInput.value = ''; }); // 重置发送按钮为初始状态 function resetSendButton() { isRequestInProgress = false; const sendBtn = document.getElementById('send-btn'); if (sendBtn) { sendBtn.textContent = '发送'; sendBtn.classList.remove('stop-button'); } } // 显示加载状态 function showLoading() { loading.classList.remove('hidden'); chatBox.scrollTop = chatBox.scrollHeight; } // 隐藏加载状态 function hideLoading() { loading.classList.add('hidden'); } // 判断内容是否为 Markdown(简单判断) function isMarkdown(content) { if (!content || content.trim() === '') return false; const markdownPatterns = [ /^#/m, // 标题 # /\*\*[^*]+\*\*/g, // 加粗 **text** /__[^_]+__/g, // 加粗 __text__ /^- /gm, // 无序列表 /^\* /gm, // 无序列表 /^\d+\. /gm, // 有序列表 /```/g // 代码块 ]; return markdownPatterns.some(pattern => pattern.test(content)); } // 处理添加消息 function addMessage(role, content, codeDiffs) { const msgDiv = document.createElement('div'); msgDiv.className = `message ${role}`; // 渲染内容 if (isMarkdown(content)) { msgDiv.innerHTML = marked.parse(content); } else { const div = document.createElement('div'); div.textContent = content; msgDiv.appendChild(div); } // 如果是AI消息且包含代码块,为每个代码块添加按钮 if (role === 'ai') { const codeBlocks = msgDiv.querySelectorAll('pre code'); // 确保 codeDiffs 是数组 const diffs = Array.isArray(codeDiffs) ? codeDiffs : []; codeBlocks.forEach((block, index) => { // 为每个代码块添加"创建文件"按钮 const createFileButton = document.createElement('button'); createFileButton.className = 'create-file-button'; createFileButton.textContent = `生成代码文件`; createFileButton.onclick = () => createNewFileFromCode(block.textContent, index); // 将按钮插入到代码块后面 block.parentNode.after(createFileButton); // 如果有代码差异,也为每个代码块添加查看差异按钮 if (diffs[index]) { const diffButton = document.createElement('button'); diffButton.className = 'diff-button'; diffButton.textContent = `查看代码差异`; diffButton.onclick = () => showCodeDiff(diffs[index]); // 将差异按钮插入到创建文件按钮后面 createFileButton.after(diffButton); } }); } chatBox.appendChild(msgDiv); // 高亮代码块 const codeBlocks = msgDiv.querySelectorAll('pre code'); codeBlocks.forEach(block => { if (hljs) { hljs.highlightElement(block); } }); // 滚动到底部 chatBox.scrollTop = chatBox.scrollHeight; return msgDiv; // 返回创建的消息元素 } // 开始流式传输 function startStreaming() { // 创建一个新的AI消息元素 streamingMessageElement = document.createElement('div'); streamingMessageElement.className = 'message ai streaming'; // 添加流式消息内容容器 const contentDiv = document.createElement('div'); contentDiv.className = 'streaming-content'; streamingMessageElement.appendChild(contentDiv); // 添加光标元素 const cursor = document.createElement('span'); cursor.className = 'streaming-cursor'; cursor.textContent = '▋'; streamingMessageElement.appendChild(cursor); chatBox.appendChild(streamingMessageElement); streamingMessageContent = ''; // 滚动到底部 chatBox.scrollTop = chatBox.scrollHeight; } // 处理流式数据 function handleStreamData(data) { if (!streamingMessageElement) return; let processedData = data; if (data.startsWith('data:')) { // 移除data:前缀 processedData = data.replace(/^data:/, ''); // 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的 processedData = processedData.replace(/^(\n{2,})/, (match) => { // 删除所有连续的换行符 return ''; }); } // 更新内容 streamingMessageContent += processedData; // 更新显示 const contentDiv = streamingMessageElement.querySelector('.streaming-content'); if (contentDiv) { // 如果是Markdown内容,我们需要特殊处理 if (isMarkdown(streamingMessageContent)) { contentDiv.innerHTML = marked.parse(streamingMessageContent); } else { contentDiv.textContent = streamingMessageContent; } // 高亮代码块 const codeBlocks = contentDiv.querySelectorAll('pre code'); codeBlocks.forEach(block => { if (hljs && !block.dataset.highlighted) { hljs.highlightElement(block); block.dataset.highlighted = 'true'; } }); } // 滚动到底部 chatBox.scrollTop = chatBox.scrollHeight; } // 结束流式传输 function endStreaming(content, codeDiffs = null) { if (streamingMessageElement) { // 移除流式传输类和光标 streamingMessageElement.classList.remove('streaming'); const cursor = streamingMessageElement.querySelector('.streaming-cursor'); if (cursor) { cursor.remove(); } // 如果是AI消息且包含代码块,为每个代码块添加按钮 if (streamingMessageElement.classList.contains('ai')) { const codeBlocks = streamingMessageElement.querySelectorAll('pre code'); codeBlocks.forEach((block, index) => { // 为每个代码块添加"创建文件"按钮 const createFileButton = document.createElement('button'); createFileButton.className = 'create-file-button'; createFileButton.textContent = `生成代码文件`; createFileButton.onclick = () => createNewFileFromCode(block.textContent, index); // 将按钮插入到代码块后面 block.parentNode.after(createFileButton); // 如果有代码差异,也为每个代码块添加查看差异按钮 // codeDiffs是一个数组,每个元素对应一个代码块的差异信息 if (codeDiffs && codeDiffs[index]) { const diffButton = document.createElement('button'); diffButton.className = 'diff-button'; diffButton.textContent = `查看代码差异`; diffButton.onclick = () => showCodeDiff(codeDiffs[index]); // 将差异按钮插入到创建文件按钮后面 createFileButton.after(diffButton); } }); } // 高亮代码块 const codeBlocks = streamingMessageElement.querySelectorAll('pre code'); codeBlocks.forEach(block => { if (hljs) { hljs.highlightElement(block); } }); streamingMessageElement = null; streamingMessageContent = ''; } // 重置发送按钮状态 resetSendButton(); // 保存消息到历史记录 currentSessionHistory.push({role: 'assistant', content: content}); hideLoading(); } // 处理请求取消 function handleRequestCancelled() { // 重置发送按钮 resetSendButton(); // 如果有正在流式传输的消息,结束它 if (streamingMessageElement) { streamingMessageElement.classList.remove('streaming'); const cursor = streamingMessageElement.querySelector('.streaming-cursor'); if (cursor) { cursor.remove(); } streamingMessageElement = null; streamingMessageContent = ''; } hideLoading(); } // 显示代码差异 function showCodeDiff(codeDiff) { const diffModal = document.getElementById('diff-modal'); const diffContent = document.getElementById('diff-content'); diffContent.innerHTML = ''; // 显示添加的行 if (codeDiff.added && codeDiff.added.length > 0) { codeDiff.added.forEach((line, index) => { const diffRow = document.createElement('div'); diffRow.className = 'diff-row'; // 修复行号显示问题 const lineNumber = document.createElement('div'); lineNumber.className = 'diff-line-number'; lineNumber.textContent = index + 1; // 使用索引+1作为行号 const diffLine = document.createElement('div'); diffLine.className = 'diff-line diff-added'; diffLine.textContent = `+ ${line}`; diffRow.appendChild(lineNumber); diffRow.appendChild(diffLine); diffContent.appendChild(diffRow); }); } // 显示删除的行 if (codeDiff.removed && codeDiff.removed.length > 0) { codeDiff.removed.forEach((line, index) => { const diffRow = document.createElement('div'); diffRow.className = 'diff-row'; // 修复行号显示问题 const lineNumber = document.createElement('div'); lineNumber.className = 'diff-line-number'; lineNumber.textContent = index + 1; // 使用索引+1作为行号 const diffLine = document.createElement('div'); diffLine.className = 'diff-line diff-removed'; diffLine.textContent = `- ${line}`; diffRow.appendChild(lineNumber); diffRow.appendChild(diffLine); diffContent.appendChild(diffRow); }); } // 添加接受和拒绝按钮的事件监听 document.getElementById('accept-changes-btn').onclick = () => acceptChanges(codeDiff.modifiedCode); document.getElementById('reject-changes-btn').onclick = () => rejectChanges(); diffModal.classList.remove('hidden'); } // 接受代码变更 function acceptChanges(modifiedCode) { if (contextFilePath) { // 发送消息到插件以接受变更 vscode.postMessage({ command: 'acceptChanges', filePath: contextFilePath, modifiedContent: modifiedCode }); // 关闭模态框 document.getElementById('diff-modal').classList.add('hidden'); } } // 拒绝代码变更 function rejectChanges() { if (contextFilePath) { // 发送消息到插件以拒绝变更 vscode.postMessage({ command: 'rejectChanges', filePath: contextFilePath }); // 关闭模态框 document.getElementById('diff-modal').classList.add('hidden'); } } // 更新工作区文件列表 function updateWorkspaceFiles(files) { const workspaceContainer = document.querySelector('.workspace-container'); const workspaceFilesContainer = document.getElementById('workspace-files'); // 如果没有文件变更,则隐藏工作区 if (!files || Object.keys(files).length === 0) { workspaceContainer.style.display = 'none'; return; } // 显示工作区 workspaceContainer.style.display = 'block'; workspaceFilesContainer.innerHTML = ''; Object.keys(files).forEach(filePath => { const fileChange = files[filePath]; const fileItem = document.createElement('div'); fileItem.className = 'workspace-file-item'; // 获取文件名 const fileName = filePath.split(/[\/\\]/).pop(); // 根据状态设置不同的显示样式 let statusText = ''; let statusClass = ''; switch (fileChange.status) { case 'accepted': statusText = '✓ 已接受'; statusClass = 'status-accepted'; break; case 'rejected': statusText = '✗ 已拒绝'; statusClass = 'status-rejected'; break; default: statusText = '待处理'; statusClass = 'status-pending'; } fileItem.innerHTML = ` ${fileName} ${statusText} `; fileItem.addEventListener('click', () => { // 发送消息到插件以打开文件 vscode.postMessage({ command: 'openWorkspaceFile', filePath: filePath }); }); workspaceFilesContainer.appendChild(fileItem); }); } window.addEventListener('message', (event) => { const message = event.data; if (message.command === 'addMessage') { addMessage(message.role, message.content, message.codeDiff); } else if (message.command === 'startStream') { // 开始流式传输 startStreaming(); } else if (message.command === 'streamData') { // 处理流式数据 handleStreamData(message.data); } else if (message.command === 'endStream') { // 结束流式传输 endStreaming(message.content, message.codeDiffs); } else if (message.command === 'requestCancelled') { // 请求被取消 handleRequestCancelled(); } else if (message.command === 'hideLoading') { hideLoading(); } else if (message.command === 'addToInput') { userInput.value = message.content; // 插入选中内容到输入框 selectedCodeForContext = message.content; userInput.focus(); // 自动聚焦输入框 } else if (message.command === 'fileList') { const fileListContainer = document.getElementById('file-list'); if (message.error) { fileListContainer.innerHTML = `