From 2b51c4cbdebb53cccb59ed3e5cdc112d3d4c68ce Mon Sep 17 00:00:00 2001 From: ZLY Date: Fri, 25 Jul 2025 15:10:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(extension):=20=E6=B7=BB=E5=8A=A0=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=96=B0=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 extension.ts 中添加了创建新文件的逻辑 - 在 webview 中增加了创建文件按钮和相关事件处理 - 重构了 callQwenAPI 和 generateCodeDiff 函数,移至新的 utils 文件夹 - 新增 getFileExtension 函数用于获取文件扩展名 --- media/webview/script.js | 73 ++++++++++++++++++-- media/webview/style.css | 16 ++++- src/extension.ts | 144 +++++++++++----------------------------- src/utils/common.ts | 83 +++++++++++++++++++++++ src/utils/modelApi.ts | 52 +++++++++++++++ 5 files changed, 255 insertions(+), 113 deletions(-) create mode 100644 src/utils/common.ts create mode 100644 src/utils/modelApi.ts diff --git a/media/webview/script.js b/media/webview/script.js index b4eea4d..fd849e9 100644 --- a/media/webview/script.js +++ b/media/webview/script.js @@ -73,6 +73,18 @@ document.addEventListener('DOMContentLoaded', () => { msgDiv.appendChild(diffButton); } + // 如果是AI消息且包含代码块,添加"创建文件"按钮 + if (role === 'ai') { + const codeBlocks = msgDiv.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); + msgDiv.appendChild(createFileButton); + }); + } + chatBox.appendChild(msgDiv); // 高亮代码块 @@ -281,10 +293,10 @@ document.addEventListener('DOMContentLoaded', () => { // 关闭模态框 document.getElementById('file-browser-modal').classList.add('hidden'); - + // 清空文件搜索输入框 document.getElementById('file-search-input').value = ''; - + // 保存当前上下文文件路径 contextFilePath = file.path; }); @@ -312,12 +324,12 @@ document.addEventListener('DOMContentLoaded', () => { } else if (message.command === 'restoreState') { // 恢复面板状态 currentSessionHistory = message.history || []; - + // 恢复显示历史消息 currentSessionHistory.forEach(msg => { addMessage(msg.role, msg.content, null); }); - + // 恢复工作区变更 if (message.workspaceChanges) { updateWorkspaceFiles(message.workspaceChanges); @@ -374,11 +386,62 @@ document.addEventListener('DOMContentLoaded', () => { 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'); } }); + // 从代码创建新文件 + function createNewFileFromCode(codeContent, index) { + // 简单的语言检测 + const language = detectLanguage(codeContent); + + // 请求创建文件 + const fileName = `new_file_${index + 1}${getFileExtension(language)}`; + + vscode.postMessage({ + command: 'createNewFile', + fileName: fileName, + content: codeContent, + language: language + }); + } + + // 简单的语言检测 + function detectLanguage(code) { + // 可以根据代码特征进行简单判断 + if (code.includes('import React') || code.includes('from react')) { + return 'javascript'; // React代码 + } else if (code.includes('public class') || code.includes('private static')) { + return 'java'; + } else if (code.includes('def ') && code.includes(':')) { + return 'python'; + } else if (code.includes('function ') || code.includes('const ') || code.includes('let ')) { + return 'javascript'; + } else if (code.includes('interface ') && code.includes('export ')) { + return 'typescript'; + } else if (code.includes(' { - const apiKey = 'dev-token'; - const url = 'https://aicomp.ngsk.tech:7001/api/v1/chat/completions'; - - const messages = [ - { - role: 'system', - content: `你是一个代码助手,请根据当前文件内容和历史对话回答问题。请使用中文回答。` - }, - ...history, - { - role: 'user', - content: `【当前文件内容】: - \`\`\`${fileContent}\`\`\` - - 【用户提问】: - ${question}` - } - ]; - - console.log("messages:",messages) - - try { - const response = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - model: 'qwen2.5-local', - messages - }) - }); - - if (!response.ok) { - throw new Error('API 请求失败'); - } - - return await response.json(); - } catch (error: any) { - console.error(error); - throw error; - } -} - -function generateCodeDiff(originalCode: string, aiResponse: string): { added: string[]; removed: string[]; modifiedCode: 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); - } - }); - - // 返回差异结果,无论是否有差异 - return { - added, - removed, - modifiedCode: normalizedModified - }; - } - - return null; -} - function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlightScriptUri:vscode.Uri,highlightStyleUri:vscode.Uri,markedScriptUri:vscode.Uri): string { return ` @@ -578,4 +508,4 @@ function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlight `; -} \ No newline at end of file +} diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..bf568fa --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,83 @@ +import * as diff from "diff"; + +export function generateCodeDiff(originalCode: string, aiResponse: string): { added: string[]; removed: string[]; modifiedCode: string } | null { + // 提取代码块中的代码 + const codeBlockRegex = /```(?:([a-zA-Z0-9]+))?\s*[\n\r]([\s\S]*?)\s*```/g; + const matches = [...aiResponse.matchAll(codeBlockRegex)]; + + 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); + + // 如果标准化后的内容相同,则没有差异 + 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); + } + }); + + // 返回差异结果,无论是否有差异 + return { + added, + removed, + modifiedCode: normalizedModified + }; + } + + return null; +} + +export function getFileExtension(language: string): string { + const languageMap: { [key: string]: string } = { + 'javascript': '.js', + 'typescript': '.ts', + 'python': '.py', + 'java': '.java', + 'html': '.html', + 'css': '.css', + 'json': '.json', + 'c': '.c', + 'cpp': '.cpp', + 'csharp': '.cs', + 'php': '.php', + 'ruby': '.rb', + 'go': '.go', + 'rust': '.rs', + 'swift': '.swift', + 'kotlin': '.kt', + 'scala': '.scala', + 'dart': '.dart', + 'lua': '.lua', + 'perl': '.pl', + 'r': '.r', + 'sql': '.sql', + 'yaml': '.yaml', + 'xml': '.xml' + }; + + return languageMap[language?.toLowerCase()] || '.txt'; +} + diff --git a/src/utils/modelApi.ts b/src/utils/modelApi.ts new file mode 100644 index 0000000..781cdbd --- /dev/null +++ b/src/utils/modelApi.ts @@ -0,0 +1,52 @@ +import vscode from "vscode"; + +export async function callQwenAPI( + question: string, + history: any[], + fileContent: string, + context: vscode.ExtensionContext +): Promise { + const apiKey = 'dev-token'; + const url = 'https://aicomp.ngsk.tech:7001/api/v1/chat/completions'; + + const messages = [ + { + role: 'system', + content: `你是一个代码助手,请根据当前文件内容和历史对话回答问题。请使用中文回答。` + }, + ...history, + { + role: 'user', + content: `【当前文件内容】: + \`\`\`${fileContent}\`\`\` + + 【用户提问】: + ${question}` + } + ]; + + console.log("messages:",messages) + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'qwen2.5-local', + messages + }) + }); + + if (!response.ok) { + throw new Error('API 请求失败'); + } + + return await response.json(); + } catch (error: any) { + console.error(error); + throw error; + } +} \ No newline at end of file