pug-site-core 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ai/aiPanel.js +0 -722
- package/lib/ai/aiRouter.js +586 -258
- package/lib/generate.js +415 -341
- package/lib/utils.js +1 -7
- package/package.json +2 -2
package/lib/ai/aiRouter.js
CHANGED
|
@@ -1,300 +1,628 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
2
|
+
import { PromptTemplate } from "@langchain/core/prompts";
|
|
3
|
+
import { StructuredOutputParser } from "@langchain/core/output_parsers";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { Document } from "@langchain/core/documents";
|
|
6
|
+
import { DirectoryLoader } from "langchain/document_loaders/fs/directory";
|
|
7
|
+
import { TextLoader } from "langchain/document_loaders/fs/text";
|
|
8
|
+
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
|
|
9
|
+
import { FileManagementToolkit } from "@langchain/community/agent_toolkits";
|
|
10
|
+
import { VectorStore } from "@langchain/core/vectorstores";
|
|
11
|
+
import { MemoryVectorStore } from "langchain/vectorstores/memory";
|
|
12
|
+
import { OpenAIEmbeddings } from "@langchain/openai";
|
|
13
|
+
import { RunnableSequence } from "@langchain/core/runnables";
|
|
14
|
+
import fs from 'fs/promises';
|
|
3
15
|
import path from 'path';
|
|
4
16
|
import { fileURLToPath } from 'url';
|
|
5
17
|
|
|
6
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
19
|
const __dirname = path.dirname(__filename);
|
|
8
|
-
const router = express.Router();
|
|
9
20
|
|
|
10
|
-
//
|
|
11
|
-
const
|
|
21
|
+
// DeepSeek API 配置
|
|
22
|
+
const llm = new ChatOpenAI({
|
|
23
|
+
model: "deepseek-chat",
|
|
24
|
+
temperature: 0.2, // 降低随机性,提高代码修改的准确性
|
|
25
|
+
openAIApiKey: process.env.DEEPSEEK_API_KEY || "your-api-key-here",
|
|
26
|
+
configuration: {
|
|
27
|
+
baseURL: "https://api.deepseek.com/v1"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
12
30
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
31
|
+
// 定义结构化输出模式
|
|
32
|
+
const LocationSchema = z.object({
|
|
33
|
+
startLine: z.number().describe("修改开始行号"),
|
|
34
|
+
endLine: z.number().describe("修改结束行号"),
|
|
35
|
+
reason: z.string().describe("选择这个位置的原因"),
|
|
36
|
+
context: z.string().describe("相关的代码上下文")
|
|
37
|
+
});
|
|
15
38
|
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
const AnalysisResultSchema = z.object({
|
|
40
|
+
locations: z.array(LocationSchema).describe("可能的修改位置列表"),
|
|
41
|
+
analysis: z.string().describe("整体分析说明"),
|
|
42
|
+
confidence: z.number().min(0).max(1).describe("分析结果的置信度")
|
|
43
|
+
});
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
const ModificationResultSchema = z.object({
|
|
46
|
+
modifiedCode: z.string().describe("修改后的代码"),
|
|
47
|
+
explanation: z.string().describe("修改说明"),
|
|
48
|
+
changes: z.array(z.object({
|
|
49
|
+
type: z.enum(["added", "modified", "deleted"]).describe("修改类型"),
|
|
50
|
+
description: z.string().describe("具体的改动描述"),
|
|
51
|
+
lineNumbers: z.array(z.number()).describe("涉及的行号")
|
|
52
|
+
})).describe("修改详情列表"),
|
|
53
|
+
riskLevel: z.enum(["low", "medium", "high"]).describe("修改风险等级")
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 使用LangChain优化的代码修改 Agent 类
|
|
58
|
+
*/
|
|
59
|
+
class EnhancedCodeModificationAgent {
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
62
|
+
this.fileManagementToolkit = new FileManagementToolkit({
|
|
63
|
+
rootDir: this.projectRoot
|
|
64
|
+
});
|
|
65
|
+
this.vectorStore = null;
|
|
66
|
+
this.embeddings = new OpenAIEmbeddings({
|
|
67
|
+
openAIApiKey: process.env.OPENAI_API_KEY
|
|
68
|
+
});
|
|
69
|
+
this.textSplitter = new RecursiveCharacterTextSplitter({
|
|
70
|
+
chunkSize: 1000,
|
|
71
|
+
chunkOverlap: 200,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 设置结构化输出解析器
|
|
75
|
+
this.analysisParser = StructuredOutputParser.fromZodSchema(AnalysisResultSchema);
|
|
76
|
+
this.modificationParser = StructuredOutputParser.fromZodSchema(ModificationResultSchema);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 初始化项目上下文向量存储
|
|
81
|
+
*/
|
|
82
|
+
async initializeProjectContext() {
|
|
83
|
+
try {
|
|
84
|
+
const loader = new DirectoryLoader(this.projectRoot, {
|
|
85
|
+
".js": (path) => new TextLoader(path),
|
|
86
|
+
".ts": (path) => new TextLoader(path),
|
|
87
|
+
".jsx": (path) => new TextLoader(path),
|
|
88
|
+
".tsx": (path) => new TextLoader(path),
|
|
89
|
+
".vue": (path) => new TextLoader(path),
|
|
90
|
+
".py": (path) => new TextLoader(path),
|
|
91
|
+
".md": (path) => new TextLoader(path),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const docs = await loader.load();
|
|
95
|
+
const splitDocs = await this.textSplitter.splitDocuments(docs);
|
|
96
|
+
|
|
97
|
+
this.vectorStore = await MemoryVectorStore.fromDocuments(
|
|
98
|
+
splitDocs,
|
|
99
|
+
this.embeddings
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
console.log(`已加载 ${docs.length} 个文件,分割为 ${splitDocs.length} 个文档块`);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('初始化项目上下文失败:', error);
|
|
105
|
+
throw error;
|
|
24
106
|
}
|
|
107
|
+
}
|
|
25
108
|
|
|
109
|
+
/**
|
|
110
|
+
* 使用LangChain工具进行文件操作
|
|
111
|
+
*/
|
|
112
|
+
async readFileWithLangChain(filePath) {
|
|
26
113
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// 初始化OpenAI模型
|
|
34
|
-
model = new ChatOpenAI({
|
|
35
|
-
modelName: "gpt-3.5-turbo",
|
|
36
|
-
temperature: 0.1,
|
|
37
|
-
});
|
|
114
|
+
const tools = this.fileManagementToolkit.getTools();
|
|
115
|
+
const readTool = tools.find(tool => tool.name === 'read_file');
|
|
116
|
+
|
|
117
|
+
if (!readTool) {
|
|
118
|
+
throw new Error('Read file tool not found');
|
|
119
|
+
}
|
|
38
120
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} catch (error) {
|
|
60
|
-
return `读取文件失败: ${error.message}`;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}),
|
|
64
|
-
|
|
65
|
-
new DynamicTool({
|
|
66
|
-
name: "write_file",
|
|
67
|
-
description: "写入或修改template-debug目录下的文件。参数格式: filepath|content (用|分隔文件路径和内容)",
|
|
68
|
-
func: async (input) => {
|
|
69
|
-
try {
|
|
70
|
-
const [filepath, ...contentParts] = input.split('|');
|
|
71
|
-
const content = contentParts.join('|');
|
|
72
|
-
|
|
73
|
-
if (!filepath || content === undefined) {
|
|
74
|
-
return "错误:请提供文件路径和内容,格式: filepath|content";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const fullPath = path.join(templateDebugPath, filepath);
|
|
78
|
-
|
|
79
|
-
// 安全检查:确保文件在template-debug目录内
|
|
80
|
-
if (!fullPath.startsWith(templateDebugPath)) {
|
|
81
|
-
return "错误:只能修改template-debug目录下的文件";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 确保目录存在
|
|
85
|
-
await fs.ensureDir(path.dirname(fullPath));
|
|
86
|
-
|
|
87
|
-
// 写入文件
|
|
88
|
-
await fs.writeFile(fullPath, content, 'utf-8');
|
|
89
|
-
|
|
90
|
-
return `文件已成功保存: ${filepath}`;
|
|
91
|
-
} catch (error) {
|
|
92
|
-
return `写入文件失败: ${error.message}`;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}),
|
|
96
|
-
|
|
97
|
-
new DynamicTool({
|
|
98
|
-
name: "list_files",
|
|
99
|
-
description: "列出template-debug目录下的文件和文件夹。参数: dirpath - 相对于template-debug的目录路径(可选,默认为根目录)",
|
|
100
|
-
func: async (dirpath = '') => {
|
|
101
|
-
try {
|
|
102
|
-
const fullPath = path.join(templateDebugPath, dirpath);
|
|
103
|
-
|
|
104
|
-
// 安全检查
|
|
105
|
-
if (!fullPath.startsWith(templateDebugPath)) {
|
|
106
|
-
return "错误:只能访问template-debug目录下的内容";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (!await fs.pathExists(fullPath)) {
|
|
110
|
-
return `目录不存在: ${dirpath || '/'}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const items = await fs.readdir(fullPath, { withFileTypes: true });
|
|
114
|
-
const result = items.map(item => {
|
|
115
|
-
const type = item.isDirectory() ? '[目录]' : '[文件]';
|
|
116
|
-
return `${type} ${item.name}`;
|
|
117
|
-
}).join('\n');
|
|
118
|
-
|
|
119
|
-
return `目录内容 (${dirpath || '/'}):\n${result}`;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
return `列出文件失败: ${error.message}`;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}),
|
|
125
|
-
|
|
126
|
-
new DynamicTool({
|
|
127
|
-
name: "delete_file",
|
|
128
|
-
description: "删除template-debug目录下的文件。参数: filepath - 相对于template-debug的文件路径",
|
|
129
|
-
func: async (filepath) => {
|
|
130
|
-
try {
|
|
131
|
-
const fullPath = path.join(templateDebugPath, filepath);
|
|
132
|
-
|
|
133
|
-
// 安全检查
|
|
134
|
-
if (!fullPath.startsWith(templateDebugPath)) {
|
|
135
|
-
return "错误:只能删除template-debug目录下的文件";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!await fs.pathExists(fullPath)) {
|
|
139
|
-
return `文件不存在: ${filepath}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
await fs.remove(fullPath);
|
|
143
|
-
return `文件已删除: ${filepath}`;
|
|
144
|
-
} catch (error) {
|
|
145
|
-
return `删除文件失败: ${error.message}`;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
// 创建prompt模板
|
|
152
|
-
const prompt = ChatPromptTemplate.fromMessages([
|
|
153
|
-
["system", `你是一个专业的代码修改助手。你可以帮助用户修改template-debug目录下的代码文件。
|
|
154
|
-
|
|
155
|
-
你有以下工具可以使用:
|
|
156
|
-
1. read_file - 读取文件内容
|
|
157
|
-
2. write_file - 写入或修改文件内容
|
|
158
|
-
3. list_files - 列出目录内容
|
|
159
|
-
4. delete_file - 删除文件
|
|
160
|
-
|
|
161
|
-
请根据用户的需求,使用这些工具来完成代码修改任务。在修改代码时,请:
|
|
162
|
-
- 仔细理解用户的需求
|
|
163
|
-
- 先读取相关文件了解现有代码结构
|
|
164
|
-
- 进行必要的修改
|
|
165
|
-
- 确保代码语法正确
|
|
166
|
-
- 提供清晰的修改说明
|
|
167
|
-
|
|
168
|
-
始终用中文回复用户。`],
|
|
169
|
-
new MessagesPlaceholder("chat_history"),
|
|
170
|
-
["human", "{input}"],
|
|
171
|
-
new MessagesPlaceholder("agent_scratchpad")
|
|
172
|
-
]);
|
|
173
|
-
|
|
174
|
-
// 创建agent
|
|
175
|
-
const agent = await createOpenAIFunctionsAgent({
|
|
176
|
-
llm: model,
|
|
177
|
-
tools,
|
|
178
|
-
prompt
|
|
179
|
-
});
|
|
121
|
+
const result = await readTool.invoke({ file_path: filePath });
|
|
122
|
+
const lines = result.split('\n');
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
content: result,
|
|
127
|
+
lines,
|
|
128
|
+
metadata: {
|
|
129
|
+
lineCount: lines.length,
|
|
130
|
+
filePath: path.resolve(this.projectRoot, filePath)
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: error.message,
|
|
137
|
+
filePath
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
180
141
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
142
|
+
/**
|
|
143
|
+
* 使用LangChain工具写入文件
|
|
144
|
+
*/
|
|
145
|
+
async writeFileWithLangChain(filePath, content) {
|
|
146
|
+
try {
|
|
147
|
+
const tools = this.fileManagementToolkit.getTools();
|
|
148
|
+
const writeTool = tools.find(tool => tool.name === 'write_file');
|
|
149
|
+
|
|
150
|
+
if (!writeTool) {
|
|
151
|
+
throw new Error('Write file tool not found');
|
|
152
|
+
}
|
|
188
153
|
|
|
189
|
-
|
|
190
|
-
|
|
154
|
+
await writeTool.invoke({
|
|
155
|
+
file_path: filePath,
|
|
156
|
+
text: content
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
message: `文件 ${filePath} 写入成功`,
|
|
162
|
+
timestamp: new Date().toISOString()
|
|
163
|
+
};
|
|
191
164
|
} catch (error) {
|
|
192
|
-
|
|
193
|
-
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
error: error.message,
|
|
168
|
+
filePath
|
|
169
|
+
};
|
|
194
170
|
}
|
|
195
|
-
}
|
|
171
|
+
}
|
|
196
172
|
|
|
197
|
-
|
|
198
|
-
|
|
173
|
+
/**
|
|
174
|
+
* 使用向量搜索获取相关上下文
|
|
175
|
+
*/
|
|
176
|
+
async getRelevantContext(description, filePath = null) {
|
|
177
|
+
if (!this.vectorStore) {
|
|
178
|
+
await this.initializeProjectContext();
|
|
179
|
+
}
|
|
199
180
|
|
|
200
|
-
// API路由
|
|
201
|
-
router.post('/chat', async (req, res) => {
|
|
202
181
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
182
|
+
// 构建搜索查询
|
|
183
|
+
let searchQuery = description;
|
|
184
|
+
if (filePath) {
|
|
185
|
+
searchQuery += ` file:${path.basename(filePath)}`;
|
|
186
|
+
}
|
|
208
187
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
188
|
+
const relevantDocs = await this.vectorStore.similaritySearch(searchQuery, 5);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
contexts: relevantDocs.map(doc => ({
|
|
193
|
+
content: doc.pageContent,
|
|
194
|
+
metadata: doc.metadata,
|
|
195
|
+
relevanceScore: doc.score || 0
|
|
196
|
+
}))
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: error.message,
|
|
202
|
+
contexts: []
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
219
206
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
207
|
+
/**
|
|
208
|
+
* 使用结构化输出分析代码位置
|
|
209
|
+
*/
|
|
210
|
+
async analyzeCodeLocationStructured(filePath, description) {
|
|
211
|
+
const fileResult = await this.readFileWithLangChain(filePath);
|
|
212
|
+
if (!fileResult.success) {
|
|
213
|
+
return { success: false, error: fileResult.error };
|
|
214
|
+
}
|
|
225
215
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
216
|
+
// 获取相关上下文
|
|
217
|
+
const contextResult = await this.getRelevantContext(description, filePath);
|
|
218
|
+
const relevantContext = contextResult.success ?
|
|
219
|
+
contextResult.contexts.map(ctx => ctx.content).join('\n\n') : '';
|
|
231
220
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
chatHistory.push(new AIMessage(result.output));
|
|
221
|
+
const analysisPrompt = PromptTemplate.fromTemplate(`
|
|
222
|
+
你是一个专业的代码分析师。请分析以下代码文件,根据用户的描述找到需要修改的具体位置。
|
|
235
223
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
224
|
+
文件路径: {filePath}
|
|
225
|
+
文件内容:
|
|
226
|
+
\`\`\`
|
|
227
|
+
{fileContent}
|
|
228
|
+
\`\`\`
|
|
240
229
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
230
|
+
相关项目上下文:
|
|
231
|
+
{relevantContext}
|
|
232
|
+
|
|
233
|
+
用户描述: {description}
|
|
234
|
+
|
|
235
|
+
请仔细分析代码结构和用户需求,返回可能的修改位置。考虑以下因素:
|
|
236
|
+
1. 代码的逻辑结构和依赖关系
|
|
237
|
+
2. 函数/类的边界
|
|
238
|
+
3. 变量作用域
|
|
239
|
+
4. 最小侵入性原则
|
|
240
|
+
|
|
241
|
+
{format_instructions}
|
|
242
|
+
`);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const chain = RunnableSequence.from([
|
|
246
|
+
analysisPrompt,
|
|
247
|
+
llm,
|
|
248
|
+
this.analysisParser
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
const result = await chain.invoke({
|
|
252
|
+
filePath,
|
|
253
|
+
fileContent: fileResult.content,
|
|
254
|
+
relevantContext,
|
|
255
|
+
description,
|
|
256
|
+
format_instructions: this.analysisParser.getFormatInstructions()
|
|
257
|
+
});
|
|
246
258
|
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
...result,
|
|
262
|
+
fileMetadata: fileResult.metadata
|
|
263
|
+
};
|
|
247
264
|
} catch (error) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
error: `分析失败: ${error.message}`,
|
|
268
|
+
fallbackLocations: this.fallbackLocationAnalysis(fileResult.lines, description)
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 降级位置分析(当结构化分析失败时)
|
|
275
|
+
*/
|
|
276
|
+
fallbackLocationAnalysis(lines, description) {
|
|
277
|
+
const keywords = description.toLowerCase().split(/\s+/);
|
|
278
|
+
const locations = [];
|
|
279
|
+
|
|
280
|
+
lines.forEach((line, index) => {
|
|
281
|
+
const lineContent = line.toLowerCase();
|
|
282
|
+
const matchCount = keywords.filter(keyword => lineContent.includes(keyword)).length;
|
|
283
|
+
|
|
284
|
+
if (matchCount > 0) {
|
|
285
|
+
locations.push({
|
|
286
|
+
startLine: index + 1,
|
|
287
|
+
endLine: index + 1,
|
|
288
|
+
reason: `包含关键词: ${keywords.filter(k => lineContent.includes(k)).join(', ')}`,
|
|
289
|
+
context: line.trim(),
|
|
290
|
+
confidence: matchCount / keywords.length
|
|
253
291
|
});
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return locations.sort((a, b) => b.confidence - a.confidence).slice(0, 3);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 生成结构化代码修改
|
|
300
|
+
*/
|
|
301
|
+
async generateCodeModificationStructured(filePath, startLine, endLine, description, context = '') {
|
|
302
|
+
const fileResult = await this.readFileWithLangChain(filePath);
|
|
303
|
+
if (!fileResult.success) {
|
|
304
|
+
return { success: false, error: fileResult.error };
|
|
254
305
|
}
|
|
255
|
-
});
|
|
256
306
|
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
const lines = fileResult.lines;
|
|
308
|
+
const beforeContext = lines.slice(Math.max(0, startLine - 6), startLine - 1).join('\n');
|
|
309
|
+
const targetCode = lines.slice(startLine - 1, endLine).join('\n');
|
|
310
|
+
const afterContext = lines.slice(endLine, Math.min(lines.length, endLine + 5)).join('\n');
|
|
311
|
+
|
|
312
|
+
// 获取项目上下文
|
|
313
|
+
const contextResult = await this.getRelevantContext(description, filePath);
|
|
314
|
+
const projectContext = contextResult.success ?
|
|
315
|
+
contextResult.contexts.slice(0, 3).map(ctx => ctx.content).join('\n\n') : '';
|
|
316
|
+
|
|
317
|
+
const modificationPrompt = PromptTemplate.fromTemplate(`
|
|
318
|
+
你是一个专业的代码修改专家。请根据用户的需求修改指定的代码片段。
|
|
319
|
+
|
|
320
|
+
文件路径: {filePath}
|
|
321
|
+
修改范围: 第{startLine}行 到 第{endLine}行
|
|
322
|
+
|
|
323
|
+
项目上下文信息:
|
|
324
|
+
{projectContext}
|
|
325
|
+
|
|
326
|
+
附加上下文:
|
|
327
|
+
{context}
|
|
328
|
+
|
|
329
|
+
修改前的代码上下文:
|
|
330
|
+
\`\`\`
|
|
331
|
+
{beforeContext}
|
|
332
|
+
--- 需要修改的代码开始 ---
|
|
333
|
+
{targetCode}
|
|
334
|
+
--- 需要修改的代码结束 ---
|
|
335
|
+
{afterContext}
|
|
336
|
+
\`\`\`
|
|
337
|
+
|
|
338
|
+
用户需求: {description}
|
|
339
|
+
|
|
340
|
+
请遵循以下原则:
|
|
341
|
+
1. 保持原有的代码风格和缩进
|
|
342
|
+
2. 确保修改后的代码语法正确
|
|
343
|
+
3. 考虑代码的完整性和逻辑性
|
|
344
|
+
4. 最小化对现有功能的影响
|
|
345
|
+
5. 添加必要的注释说明
|
|
346
|
+
|
|
347
|
+
{format_instructions}
|
|
348
|
+
`);
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const chain = RunnableSequence.from([
|
|
352
|
+
modificationPrompt,
|
|
353
|
+
llm,
|
|
354
|
+
this.modificationParser
|
|
355
|
+
]);
|
|
356
|
+
|
|
357
|
+
const result = await chain.invoke({
|
|
358
|
+
filePath,
|
|
359
|
+
startLine,
|
|
360
|
+
endLine,
|
|
361
|
+
beforeContext,
|
|
362
|
+
targetCode,
|
|
363
|
+
afterContext,
|
|
364
|
+
description,
|
|
365
|
+
context,
|
|
366
|
+
projectContext,
|
|
367
|
+
format_instructions: this.modificationParser.getFormatInstructions()
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
success: true,
|
|
372
|
+
...result,
|
|
373
|
+
originalCode: targetCode,
|
|
374
|
+
metadata: fileResult.metadata
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return {
|
|
378
|
+
success: false,
|
|
379
|
+
error: `生成修改失败: ${error.message}`,
|
|
380
|
+
originalCode: targetCode
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* 应用代码修改(使用LangChain工具)
|
|
387
|
+
*/
|
|
388
|
+
async applyModification(filePath, startLine, endLine, newCode) {
|
|
389
|
+
const fileResult = await this.readFileWithLangChain(filePath);
|
|
390
|
+
if (!fileResult.success) {
|
|
391
|
+
return { success: false, error: fileResult.error };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const lines = fileResult.lines;
|
|
396
|
+
const newLines = [
|
|
397
|
+
...lines.slice(0, startLine - 1),
|
|
398
|
+
...newCode.split('\n'),
|
|
399
|
+
...lines.slice(endLine)
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
const newContent = newLines.join('\n');
|
|
403
|
+
const writeResult = await this.writeFileWithLangChain(filePath, newContent);
|
|
404
|
+
|
|
405
|
+
if (writeResult.success) {
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
message: `成功修改文件 ${filePath}`,
|
|
409
|
+
changes: {
|
|
410
|
+
linesAdded: newCode.split('\n').length,
|
|
411
|
+
linesRemoved: endLine - startLine + 1,
|
|
412
|
+
originalRange: { startLine, endLine },
|
|
413
|
+
modifiedAt: writeResult.timestamp
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
} else {
|
|
417
|
+
return { success: false, error: writeResult.error };
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
error: `应用修改失败: ${error.message}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 智能代码修改 - 使用所有LangChain优化
|
|
429
|
+
*/
|
|
430
|
+
async smartModifyWithEnhancements(filePath, description, autoApply = false) {
|
|
431
|
+
console.log(`开始智能分析文件: ${filePath}`);
|
|
432
|
+
|
|
259
433
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
434
|
+
// 1. 使用结构化输出分析代码位置
|
|
435
|
+
const locationResult = await this.analyzeCodeLocationStructured(filePath, description);
|
|
436
|
+
if (!locationResult.success) {
|
|
437
|
+
return { success: false, error: locationResult.error };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
console.log(`找到 ${locationResult.locations.length} 个可能的修改位置 (置信度: ${locationResult.confidence})`);
|
|
441
|
+
|
|
442
|
+
const results = [];
|
|
443
|
+
|
|
444
|
+
// 2. 对每个位置生成结构化修改
|
|
445
|
+
for (const location of locationResult.locations) {
|
|
446
|
+
console.log(`正在处理位置: 第${location.startLine}-${location.endLine}行`);
|
|
447
|
+
|
|
448
|
+
const modificationResult = await this.generateCodeModificationStructured(
|
|
449
|
+
filePath,
|
|
450
|
+
location.startLine,
|
|
451
|
+
location.endLine,
|
|
452
|
+
description,
|
|
453
|
+
location.context
|
|
454
|
+
);
|
|
262
455
|
|
|
263
|
-
if (
|
|
264
|
-
|
|
456
|
+
if (modificationResult.success) {
|
|
457
|
+
const result = {
|
|
458
|
+
location,
|
|
459
|
+
modification: modificationResult,
|
|
460
|
+
applied: false,
|
|
461
|
+
riskAssessment: {
|
|
462
|
+
level: modificationResult.riskLevel,
|
|
463
|
+
considerations: this.assessModificationRisk(modificationResult)
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// 如果启用自动应用且风险等级较低
|
|
468
|
+
if (autoApply && modificationResult.riskLevel === 'low') {
|
|
469
|
+
const applyResult = await this.applyModification(
|
|
470
|
+
filePath,
|
|
471
|
+
location.startLine,
|
|
472
|
+
location.endLine,
|
|
473
|
+
modificationResult.modifiedCode
|
|
474
|
+
);
|
|
475
|
+
result.applied = applyResult.success;
|
|
476
|
+
result.applyResult = applyResult;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
results.push(result);
|
|
265
480
|
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
success: true,
|
|
485
|
+
analysis: locationResult.analysis,
|
|
486
|
+
confidence: locationResult.confidence,
|
|
487
|
+
modifications: results,
|
|
488
|
+
projectContextUsed: true
|
|
489
|
+
};
|
|
490
|
+
} catch (error) {
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
error: `智能修改失败: ${error.message}`,
|
|
494
|
+
fallbackAvailable: true
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 评估修改风险
|
|
501
|
+
*/
|
|
502
|
+
assessModificationRisk(modificationResult) {
|
|
503
|
+
const considerations = [];
|
|
504
|
+
|
|
505
|
+
// 检查修改复杂度
|
|
506
|
+
const changeCount = modificationResult.changes.length;
|
|
507
|
+
if (changeCount > 5) {
|
|
508
|
+
considerations.push("修改涉及多处变更,建议仔细审查");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// 检查是否涉及删除操作
|
|
512
|
+
const hasDeletes = modificationResult.changes.some(change => change.type === 'deleted');
|
|
513
|
+
if (hasDeletes) {
|
|
514
|
+
considerations.push("包含代码删除操作,请确认不会影响现有功能");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 检查修改说明的完整性
|
|
518
|
+
if (!modificationResult.explanation || modificationResult.explanation.length < 20) {
|
|
519
|
+
considerations.push("修改说明较简单,建议获取更详细的解释");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return considerations;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 批量修改多个文件 - 增强版
|
|
527
|
+
*/
|
|
528
|
+
async batchModifyEnhanced(modifications, options = {}) {
|
|
529
|
+
const { autoApply = false, maxConcurrency = 3 } = options;
|
|
530
|
+
const results = [];
|
|
531
|
+
|
|
532
|
+
// 使用Promise控制并发数量
|
|
533
|
+
const semaphore = new Array(maxConcurrency).fill(null);
|
|
534
|
+
|
|
535
|
+
const processModification = async (mod) => {
|
|
536
|
+
try {
|
|
537
|
+
let result;
|
|
266
538
|
|
|
267
|
-
if (
|
|
268
|
-
|
|
539
|
+
if (mod.startLine && mod.endLine) {
|
|
540
|
+
// 指定位置修改
|
|
541
|
+
const modResult = await this.generateCodeModificationStructured(
|
|
542
|
+
mod.filePath,
|
|
543
|
+
mod.startLine,
|
|
544
|
+
mod.endLine,
|
|
545
|
+
mod.description
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
if (modResult.success && autoApply && modResult.riskLevel === 'low') {
|
|
549
|
+
const applyResult = await this.applyModification(
|
|
550
|
+
mod.filePath,
|
|
551
|
+
mod.startLine,
|
|
552
|
+
mod.endLine,
|
|
553
|
+
modResult.modifiedCode
|
|
554
|
+
);
|
|
555
|
+
result = { ...modResult, applied: applyResult.success, applyResult };
|
|
556
|
+
} else {
|
|
557
|
+
result = { ...modResult, applied: false };
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
// 智能修改
|
|
561
|
+
result = await this.smartModifyWithEnhancements(
|
|
562
|
+
mod.filePath,
|
|
563
|
+
mod.description,
|
|
564
|
+
autoApply
|
|
565
|
+
);
|
|
269
566
|
}
|
|
270
567
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
568
|
+
return {
|
|
569
|
+
filePath: mod.filePath,
|
|
570
|
+
description: mod.description,
|
|
571
|
+
result,
|
|
572
|
+
processedAt: new Date().toISOString()
|
|
573
|
+
};
|
|
277
574
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
575
|
+
} catch (error) {
|
|
576
|
+
return {
|
|
577
|
+
filePath: mod.filePath,
|
|
578
|
+
description: mod.description,
|
|
579
|
+
result: { success: false, error: error.message },
|
|
580
|
+
processedAt: new Date().toISOString()
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// 并发处理修改请求
|
|
586
|
+
const chunks = [];
|
|
587
|
+
for (let i = 0; i < modifications.length; i += maxConcurrency) {
|
|
588
|
+
chunks.push(modifications.slice(i, i + maxConcurrency));
|
|
281
589
|
}
|
|
282
|
-
});
|
|
283
590
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
591
|
+
for (const chunk of chunks) {
|
|
592
|
+
const promises = chunk.map(processModification);
|
|
593
|
+
const chunkResults = await Promise.all(promises);
|
|
594
|
+
results.push(...chunkResults);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
results,
|
|
600
|
+
summary: {
|
|
601
|
+
total: results.length,
|
|
602
|
+
successful: results.filter(r => r.result.success).length,
|
|
603
|
+
applied: results.filter(r => r.result.applied).length,
|
|
604
|
+
failed: results.filter(r => !r.result.success).length
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
290
608
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
609
|
+
/**
|
|
610
|
+
* 清理资源
|
|
611
|
+
*/
|
|
612
|
+
async cleanup() {
|
|
613
|
+
if (this.vectorStore) {
|
|
614
|
+
// 清理向量存储资源
|
|
615
|
+
this.vectorStore = null;
|
|
616
|
+
}
|
|
617
|
+
console.log('代码修改Agent资源已清理');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// 创建增强的代码修改 Agent 实例
|
|
622
|
+
const enhancedCodeAgent = new EnhancedCodeModificationAgent();
|
|
623
|
+
|
|
624
|
+
// 导出功能
|
|
625
|
+
export { enhancedCodeAgent, EnhancedCodeModificationAgent };
|
|
299
626
|
|
|
300
|
-
|
|
627
|
+
// 默认导出
|
|
628
|
+
export default enhancedCodeAgent;
|