这个任务的核心分为三个部分:
构建一个智能的 Prompt: 我们不能只把用户的要求发给 OpenAI。我们需要将用户的要求、上下文(选中的代码或光标周围的代码)、以及明确的指令(比如“只返回代码”)打包成一个高质量的“提示工程”(Prompt Engineering)字符串。
安全的后端代理: 永远不要在前端暴露你的 OpenAI API 密钥。你需要一个简单的后端服务来接收前端的请求,附加上你的密钥,然后安全地调用 OpenAI API,并将结果流式传回给前端。
前端的交互逻辑: 前端负责处理用户输入、管理编辑器状态、调用后端代理,并以流式的方式将结果渲染回编辑器。
下面,我将提供一个完整的、可直接运行的前端实现,并为您提供一个非常简单的后端代理示例(使用 Node.js)。
第 1 步:前端实现 (HTML + JS)
这个文件包含了所有用户能看到和交互的部分。它会模拟后端调用,让你即使没有后端也能测试前端的完整逻辑。
ai-editor.html 文件:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AI 智能代码助手 (ACE Editor + OpenAI)</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; display: flex; flex-direction: column; height: 100vh; background-color: #f4f7f9; } .header { padding: 10px 15px; background: #fff; border-bottom: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0,0,0,0.05); z-index: 10; } .main-content { display: flex; flex-grow: 1; } #editor { width: 100%; height: 100%; font-size: 14px; } .control-panel { padding: 15px; background: #fff; border-top: 1px solid #ddd; display: flex; align-items: center; gap: 15px; } #ai-prompt-input { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 5px; font-size: 14px; } .btn { padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } .btn-primary { background-color: #007bff; color: white; } .btn-primary:hover { background-color: #0056b3; } .btn-danger { background-color: #dc3545; color: white; } .btn-danger:hover { background-color: #c82333; } </style> </head> <body> <header class="header"> <h1>AI 智能代码助手</h1> </header> <main class="main-content"> <div id="editor"></div> </main> <footer class="control-panel"> <input type="text" id="ai-prompt-input" placeholder="输入您的要求,例如:将此函数转换为箭头函数"> <button id="run-ai-btn" class="btn btn-primary">执行 AI 操作</button> <button id="cancel-ai-btn" class="btn btn-danger" style="display: none;">取消</button> </footer> <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.15.2/ace.js"></script> <script> // --- 1. 初始化 ACE 编辑器 --- var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.session.setMode("ace/mode/javascript"); editor.setValue(`// 示例代码:您可以选中代码或将光标放在某处 function calculateFactorial(n) { // 这是一个计算阶乘的函数 if (n < 0) { return "错误:不支持负数"; } let result = 1; for (let i = 2; i <= n; i++) { result *= i; } return result; } console.log('光标放在这里,然后输入 "创建一个打印 hello world 的函数" 试试'); `, -1); // --- 2. 获取 UI 元素 --- var runAiBtn = document.getElementById("run-ai-btn"); var cancelAiBtn = document.getElementById("cancel-ai-btn"); var promptInput = document.getElementById("ai-prompt-input"); var isRequestActive = false; var isRequestCancelled = false; // --- 3. 核心功能:AI 任务处理器 --- function runAIAssist() { if (isRequestActive) { alert("AI 正在处理中,请稍候..."); return; } var userInstruction = promptInput.value.trim(); if (!userInstruction) { alert("请输入您的要求!"); return; } // --- 准备编辑器和状态 --- var session = editor.getSession(); var doc = session.getDocument(); var undoManager = session.getUndoManager(); // --- 构建智能 Prompt --- var selectionRange = editor.getSelectionRange(); var selectedText = editor.getSelectedText(); var fullCode = editor.getValue(); var engineeredPrompt = constructOpenAIPrompt(userInstruction, selectedText, fullCode, selectionRange.start); // --- 开始请求流程 --- isRequestActive = true; isRequestCancelled = false; runAiBtn.disabled = true; promptInput.disabled = true; cancelAiBtn.style.display = "inline-block"; cancelAiBtn.onclick = function() { console.log("Cancel button clicked."); isRequestCancelled = true; }; // --- 开始撤销组并处理选区 --- undoManager.startNewGroup(); var insertionAnchor; if (!selectionRange.isEmpty()) { insertionAnchor = selectionRange.start; doc.remove(selectionRange); editor.clearSelection(); } else { insertionAnchor = editor.getCursorPosition(); } // --- 调用后端(或模拟后端) --- streamAIResponse(engineeredPrompt, { onStream: function(contentChunk) { if (contentChunk) { doc.insert(insertionAnchor, contentChunk); var endPos = doc.indexToPosition(doc.positionToIndex(insertionAnchor) + contentChunk.length); insertionAnchor = endPos; editor.moveCursorTo(insertionAnchor.row, insertionAnchor.column); } }, onComplete: function(reason) { cleanup("completed: " + reason); }, onCancelCheck: function() { return isRequestCancelled; } }); editor.setReadOnly(true); } function cleanup(reason) { if (!isRequestActive) return; isRequestActive = false; console.log("Cleanup reason:", reason); editor.getSession().getUndoManager().markIgnored(); editor.setReadOnly(false); runAiBtn.disabled = false; promptInput.disabled = false; cancelAiBtn.style.display = "none"; cancelAiBtn.onclick = null; editor.focus(); } // --- 4. Prompt 工程:构建高质量的请求 --- function constructOpenAIPrompt(instruction, selectedCode, fullCode, cursorPos) { var language = editor.session.getMode().$id.split("/").pop(); // e.g., 'javascript' var systemMessage = `You are an expert ${language} programmer and a helpful AI code assistant. Your task is to help the user by writing new code or modifying existing code based on their instructions. IMPORTANT: Only output the raw code. Do not include any explanations, comments, or markdown code fences like \`\`\`javascript.`; var userMessage; if (selectedCode) { // 修改/重写场景 userMessage = `The user has selected the following code block:\n\n${selectedCode}\n\nTheir instruction is: "${instruction}".\n\nPlease provide the complete, modified version of the code block.`; } else { // 插入/生成新代码场景 userMessage = `The user's cursor is at line ${cursorPos.row + 1}, column ${cursorPos.column + 1}. The full code of the file is:\n\n${fullCode}\n\nTheir instruction is: "${instruction}".\n\nPlease provide the new code that should be inserted at the user's cursor position.`; } console.log("Engineered Prompt:", { system: systemMessage, user: userMessage }); // 返回一个可以发送到后端的对象 return { model: "gpt-4-turbo-preview", // 或者 "gpt-3.5-turbo" messages: [ { role: "system", content: systemMessage }, { role: "user", content: userMessage } ], stream: true }; } // --- 5. 后端调用与流式处理 --- function streamAIResponse(payload, callbacks) { // ================================================================= // !!! 警告:这是一个模拟后端 !!! // 在真实应用中,这里应该是调用您自己后端服务器的 fetch/ajax 请求。 // 永远不要在前端直接调用 OpenAI API 或暴露您的 API 密钥。 // ================================================================= console.log("Calling FAKE backend with payload:", payload); // 模拟一个流式响应 var fakeResponseChunks = ["function helloWorld() {\n", " console.log('Hello, ", "World!');\n", "}"]; var chunkIndex = 0; var intervalId = setInterval(function() { // 检查是否被取消 if (callbacks.onCancelCheck()) { clearInterval(intervalId); callbacks.onComplete("cancelled"); return; } if (chunkIndex < fakeResponseChunks.length) { callbacks.onStream(fakeResponseChunks[chunkIndex]); chunkIndex++; } else { // 流结束 clearInterval(intervalId); callbacks.onComplete("finished"); } }, 200); // 每 200ms 发送一小块 } // --- 绑定事件 --- runAiBtn.addEventListener("click", runAIAssist); promptInput.addEventListener("keydown", function(e) { if (e.key === "Enter") { runAIAssist(); } }); </script> </body> </html>
第 2 步:后端代理实现 (Node.js + Express)
这是在真实项目中必须创建的部分。它非常简单,主要作用就是隐藏你的API密钥。
项目初始化
mkdir my-ai-backend cd my-ai-backend npm init -y npm install express openai body-parser cors
创建 server.js 文件
// server.js const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const OpenAI = require('openai'); // **重要**: 从环境变量中获取API密钥,不要硬编码 const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); const app = express(); const port = 3000; app.use(cors()); // 允许跨域请求 app.use(bodyParser.json()); // 创建我们的核心API端点 app.post('/api/ai-assist', async (req, res) => { try { const { model, messages } = req.body; console.log('Received request for model:', model); // 调用 OpenAI API 并请求流式响应 const stream = await openai.chat.completions.create({ model: model, messages: messages, stream: true, }); // 设置响应头,告诉浏览器这是一个事件流 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 遍历流并将每一块数据直接转发给前端 for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ''; if (content) { // 按照 SSE (Server-Sent Events) 格式发送 res.write(`data: ${JSON.stringify({ content })}\n\n`); } } // 流结束后,发送一个完成信号(可选但推荐) res.write(`data: [DONE]\n\n`); res.end(); } catch (error) { console.error('Error calling OpenAI API:', error); res.status(500).json({ error: 'Failed to call AI service' }); } }); app.listen(port, () => { console.log(`AI backend proxy listening on http://localhost:${port}`); });
如何运行后端
在终端设置你的API密钥:
Mac/Linux: export OPENAI_API_KEY='sk-...'
Windows (CMD): set OPENAI_API_KEY=sk-...
Windows (PowerShell): $env:OPENAI_API_KEY='sk-...'
启动服务器: node server.js
第 3 步:连接前端与后端
当你的后端运行时,只需修改前端 ai-editor.html 文件中的 streamAIResponse 函数,将其从模拟改为真实调用。
修改后的 streamAIResponse 函数:
// --- 5. 后端调用与流式处理 --- async function streamAIResponse(payload, callbacks) { try { const response = await fetch('http://localhost:3000/api/ai-assist', { // 调用你的后端代理 method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), // 注意:真实场景下取消逻辑需要 AbortController // 但为了与之前的 ES5 兼容代码保持一致,我们继续用 onCancelCheck }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ''; while (true) { // 检查是否被取消 if (callbacks.onCancelCheck()) { reader.cancel(); callbacks.onComplete("cancelled"); break; } const { value, done } = await reader.read(); if (done) { callbacks.onComplete("finished"); break; } buffer += decoder.decode(value, { stream: true }); let boundary; while ((boundary = buffer.indexOf('\n\n')) !== -1) { const line = buffer.substring(0, boundary).trim(); buffer = buffer.substring(boundary + 2); if (line.startsWith('data: ')) { const jsonStr = line.substring(6).trim(); if (jsonStr === '[DONE]') { // 由 reader.read() 的 done 状态处理,这里可以忽略 continue; } try { const parsed = JSON.parse(jsonStr); callbacks.onStream(parsed.content); } catch (e) { console.error('Error parsing stream JSON:', e); } } } } } catch (error) { console.error("Failed to fetch AI response:", error); alert("调用 AI 服务失败: " + error.message); callbacks.onComplete("error"); } }
现在你拥有了一个功能完整、安全、体验优秀的 AI 代码助手!
网友回复
acejs代码编辑器如何调用openai api实现选择代码修改与代码自动补全?
ace.js如何获取选择文本的开始和结束行数?
如何把qwen code cli或gemini cli的免费调用额度换成http api对外开放接口?
如何限制windows10电脑只能打开指定的程序?
python如何调用ai大模型实现web网页系统的功能测试并生成测试报告?
有没有免费进行web网站ai仿真人测试生成测试报告的mcp服务或api?
Context Engineering到底是啥,有什么用?
如何使用Google veo 3+高斯溅射(Gaussian Splatting)技术生成4d视频?
浏览器中如何实时调用摄像头扫描二维码?
grok4、gemini2.5pro、gpt5、claude4.1到底谁的编程能力更强一些?