diff --git a/media/webview/script.js b/media/webview/script.js index 82c929a..89237f2 100644 --- a/media/webview/script.js +++ b/media/webview/script.js @@ -40,21 +40,31 @@ document.addEventListener('DOMContentLoaded', () => { return markdownPatterns.some(pattern => pattern.test(content)); } - function addMessage(role, content) { + // 处理添加消息 + function addMessage(role, content, codeDiff) { const msgDiv = document.createElement('div'); msgDiv.className = `message ${role}`; // 渲染内容 if (isMarkdown(content)) { - console.log("content:", content) + console.log("content:", content); msgDiv.innerHTML = marked.parse(content); - console.log("msgDiv.innerHTML:", msgDiv.innerHTML) + console.log("msgDiv.innerHTML:", msgDiv.innerHTML); } else { const div = document.createElement('div'); div.textContent = content; msgDiv.appendChild(div); } + // 如果有代码差异,添加查看差异按钮 + if (codeDiff) { + const diffButton = document.createElement('button'); + diffButton.className = 'diff-button'; + diffButton.textContent = '查看代码差异'; + diffButton.onclick = () => showCodeDiff(codeDiff); + msgDiv.appendChild(diffButton); + } + chatBox.appendChild(msgDiv); // 高亮代码块 @@ -67,6 +77,36 @@ document.addEventListener('DOMContentLoaded', () => { chatBox.scrollTop = chatBox.scrollHeight; } + // 显示代码差异 + 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 => { + const diffLine = document.createElement('div'); + diffLine.className = 'diff-line diff-added'; + diffLine.textContent = `+ ${line}`; + diffContent.appendChild(diffLine); + }); + } + + // 显示删除的行 + if (codeDiff.removed && codeDiff.removed.length > 0) { + codeDiff.removed.forEach(line => { + const diffLine = document.createElement('div'); + diffLine.className = 'diff-line diff-removed'; + diffLine.textContent = `- ${line}`; + diffContent.appendChild(diffLine); + }); + } + + diffModal.classList.remove('hidden'); + } + function showLoading() { loading.classList.remove('hidden'); } @@ -79,7 +119,7 @@ document.addEventListener('DOMContentLoaded', () => { const message = event.data; console.log(message) if (message.command === 'addMessage') { - addMessage(message.role, message.content); + addMessage(message.role, message.content,message.codeDiff); } else if (message.command === 'hideLoading') { hideLoading(); } else if (message.command === 'addToInput') { @@ -182,4 +222,15 @@ document.addEventListener('DOMContentLoaded', () => { contextPlaceholder.classList.remove('hidden'); selectedCodeForContext = ''; }); + // 关闭差异模态框 + document.getElementById('close-diff-modal').addEventListener('click', () => { + document.getElementById('diff-modal').classList.add('hidden'); + }); + + // 点击模态框外部关闭 + document.getElementById('diff-modal').addEventListener('click', (e) => { + if (e.target.id === 'diff-modal') { + document.getElementById('diff-modal').classList.add('hidden'); + } + }); }); \ No newline at end of file diff --git a/media/webview/style.css b/media/webview/style.css index 9f370cb..3f38981 100644 --- a/media/webview/style.css +++ b/media/webview/style.css @@ -322,3 +322,49 @@ body { padding: 20px; color: #abb2bf; } +.diff-modal-content { + width: 90%; + max-width: 900px; +} + +.diff-content { + font-family: 'Courier New', monospace; + font-size: 14px; + line-height: 1.4; + background-color: #1e1e1e; + padding: 15px; + border-radius: 4px; + overflow-x: auto; +} + +.diff-line { + white-space: pre; +} + +.diff-added { + background-color: rgba(0, 255, 0, 0.1); + color: #4ec9b0; +} + +.diff-removed { + background-color: rgba(255, 0, 0, 0.1); + color: #f48771; +} + +.diff-unchanged { + color: #d4d4d4; +} +.diff-button { + margin-top: 10px; + padding: 5px 10px; + background-color: #007acc; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; + font-size: 12px; +} + +.diff-button:hover { + background-color: #005a9e; +} diff --git a/src/extension.ts b/src/extension.ts index 6e719b9..c7a2b7c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,10 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as diff from 'diff'; let panel: vscode.WebviewPanel | undefined; +let currentSessionHistory: { role: string; content: string }[] = []; + export function activate(context: vscode.ExtensionContext) { console.log('Extension activated'); @@ -127,6 +130,9 @@ function openWebview( globalState: vscode.Memento, context: vscode.ExtensionContext // 添加 context 参数 ) { + // 每次打开新的 Webview 时清空当前会话历史 + currentSessionHistory = []; + panel = vscode.window.createWebviewPanel( 'aiChatWebview', 'AI Chat', @@ -166,22 +172,30 @@ function openWebview( case 'ask': const question = message.text; const fileContent = message.fileContent; - const history = globalState.get<{ role: string; content: string }[]>('chatHistory', []); + const history = currentSessionHistory; - const newHistory = [...history, { role: 'user', content: question }]; - globalState.update('chatHistory', newHistory); + currentSessionHistory = [...history, { role: 'user', content: question }]; panel.webview.postMessage({ command: 'addMessage', role: 'user', content: question }); try { const response = await callQwenAPI(question, history, fileContent, context); - console.log("response:",response) + console.log("response:", response); const aiMessage = response.choices[0]?.message?.content || '未获取到回复'; - const updatedHistory = [...newHistory, { role: 'assistant', content: aiMessage }]; - globalState.update('chatHistory', updatedHistory); + // 检查是否包含代码修改 + const codeDiff = generateCodeDiff(fileContent, aiMessage); + console.log("codeDiff:",codeDiff) + + // 更新当前会话历史 + currentSessionHistory = [...currentSessionHistory, { role: 'assistant', content: aiMessage }]; - panel.webview.postMessage({ command: 'addMessage', role: 'ai', content: aiMessage }); + panel.webview.postMessage({ + command: 'addMessage', + role: 'ai', + content: aiMessage, + codeDiff: codeDiff // 发送代码差异信息 + }); } catch (error) { panel.webview.postMessage({ command: 'addMessage', role: 'ai', content: '调用失败:' + error.message }); } @@ -253,6 +267,13 @@ function openWebview( undefined, context.subscriptions ); + + // 添加 dispose 监听器 + panel.onDidDispose(() => { + // 面板关闭时清空当前会话历史 + currentSessionHistory = []; + panel = undefined; + }, null, context.subscriptions); } async function callQwenAPI( @@ -269,6 +290,7 @@ async function callQwenAPI( role: 'system', content: `你是一个代码助手,请根据当前文件内容和历史对话回答问题。请使用中文回答。` }, + ...history, { role: 'user', content: `【当前文件内容】: @@ -305,6 +327,58 @@ async function callQwenAPI( } } +function generateCodeDiff(originalCode: string, aiResponse: string): { added: string[]; removed: string[] } | null { + console.log("模型返回值:",aiResponse) + // 提取代码块中的代码 + const codeBlockRegex = /```(?:([a-zA-Z0-9]+))?\s*[\n\r]([\s\S]*?)\s*```/g; + const matches = [...aiResponse.matchAll(codeBlockRegex)]; + console.log("正则过滤:",matches) + + if (matches.length > 0) { + const modifiedCode = matches[0][2]; // 提取第一个代码块中的代码 + + // 智能标准化函数 + const smartNormalize = (code: string): string => { + return code + .split('\n') + .map(line => line.trimEnd()) // 使用 trimEnd() 移除行尾空白 + .filter(line => line !== null && line !== undefined) // 过滤空行 + .join('\n') + .trim(); // 整体去除首尾空白 + }; + + const normalizedOriginal = smartNormalize(originalCode); + const normalizedModified = smartNormalize(modifiedCode); + console.log("原始内容:",normalizedOriginal) + console.log("模型内容:",normalizedModified) + + // 如果标准化后的内容相同,则没有差异 + if (normalizedOriginal === normalizedModified) { + return null; + } + // 使用更精确的 diff 算法 + const diffResult = diff.diffLines(normalizedOriginal, normalizedModified, { ignoreWhitespace: true }); + + const added: string[] = []; + const removed: string[] = []; + + diffResult.forEach(part => { + if (part.added) { + added.push(part.value); + } else if (part.removed) { + removed.push(part.value); + } + }); + + // 只有当确实有差异时才返回 + if (added.length > 0 || removed.length > 0) { + return { added, removed }; + } + } + + return null; +} + function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlightScriptUri:vscode.Uri,highlightStyleUri:vscode.Uri,markedScriptUri:vscode.Uri): string { return ` @@ -353,6 +427,19 @@ function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlight + +
+