+
14
-

回答

这个任务的核心分为三个部分:

构建一个智能的 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 代码助手!

网友回复

我知道答案,我要回答