mc-ai-code-review 1.1.24
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/bin/ai-code-review.js +28 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/formatters/index.d.ts +5 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +21 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/rdjson.d.ts +17 -0
- package/dist/formatters/rdjson.d.ts.map +1 -0
- package/dist/formatters/rdjson.js +65 -0
- package/dist/formatters/rdjson.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/performanceReviewer/index.d.ts +17 -0
- package/dist/performanceReviewer/index.d.ts.map +1 -0
- package/dist/performanceReviewer/index.js +251 -0
- package/dist/performanceReviewer/index.js.map +1 -0
- package/dist/reviewer/index.d.ts +10 -0
- package/dist/reviewer/index.d.ts.map +1 -0
- package/dist/reviewer/index.js +115 -0
- package/dist/reviewer/index.js.map +1 -0
- package/dist/reviewer/modelAdapters/base.d.ts +14 -0
- package/dist/reviewer/modelAdapters/base.d.ts.map +1 -0
- package/dist/reviewer/modelAdapters/base.js +3 -0
- package/dist/reviewer/modelAdapters/base.js.map +1 -0
- package/dist/reviewer/modelAdapters/factory.d.ts +13 -0
- package/dist/reviewer/modelAdapters/factory.d.ts.map +1 -0
- package/dist/reviewer/modelAdapters/factory.js +21 -0
- package/dist/reviewer/modelAdapters/factory.js.map +1 -0
- package/dist/reviewer/modelAdapters/gemini.d.ts +11 -0
- package/dist/reviewer/modelAdapters/gemini.d.ts.map +1 -0
- package/dist/reviewer/modelAdapters/gemini.js +61 -0
- package/dist/reviewer/modelAdapters/gemini.js.map +1 -0
- package/dist/reviewer/modelAdapters/openai.d.ts +12 -0
- package/dist/reviewer/modelAdapters/openai.d.ts.map +1 -0
- package/dist/reviewer/modelAdapters/openai.js +54 -0
- package/dist/reviewer/modelAdapters/openai.js.map +1 -0
- package/dist/reviewer/parseResponse.d.ts +10 -0
- package/dist/reviewer/parseResponse.d.ts.map +1 -0
- package/dist/reviewer/parseResponse.js +97 -0
- package/dist/reviewer/parseResponse.js.map +1 -0
- package/dist/reviewer/utils.d.ts +44 -0
- package/dist/reviewer/utils.d.ts.map +1 -0
- package/dist/reviewer/utils.js +392 -0
- package/dist/reviewer/utils.js.map +1 -0
- package/dist/summarizer/index.d.ts +10 -0
- package/dist/summarizer/index.d.ts.map +1 -0
- package/dist/summarizer/index.js +142 -0
- package/dist/summarizer/index.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/dist/utils.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI Code Performance Reviewer 核心逻辑
|
|
4
|
+
*
|
|
5
|
+
* 与 reviewer 共享 diff 解析、响应解析、评论对齐等基础设施,
|
|
6
|
+
* 仅替换 prompt 和规则为性能审查专用版本。
|
|
7
|
+
*
|
|
8
|
+
* 与代码规范审查的区别:
|
|
9
|
+
* - 按单文件粒度调用模型(而非整个 diff 一次调用)
|
|
10
|
+
* - 仅审查 js/ts/tsx/jsx/vue 文件
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.reviewPerformance = void 0;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const utils_1 = require("../reviewer/utils");
|
|
50
|
+
const factory_1 = require("../reviewer/modelAdapters/factory");
|
|
51
|
+
const parseResponse_1 = require("../reviewer/parseResponse");
|
|
52
|
+
/** 性能审查仅针对以下文件后缀 */
|
|
53
|
+
const PERFORMANCE_REVIEW_EXTENSIONS = [
|
|
54
|
+
".js",
|
|
55
|
+
".ts",
|
|
56
|
+
".tsx",
|
|
57
|
+
".jsx",
|
|
58
|
+
".vue",
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* 判断文件路径是否属于性能审查的目标文件类型
|
|
62
|
+
*/
|
|
63
|
+
const isReviewableFile = (filePath) => {
|
|
64
|
+
const lower = filePath.toLowerCase();
|
|
65
|
+
return PERFORMANCE_REVIEW_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
66
|
+
};
|
|
67
|
+
const performanceRules = [
|
|
68
|
+
"重点关注算法时间/空间复杂度问题,如不必要的 O(n²) 循环、递归无终止条件",
|
|
69
|
+
"检查是否存在内存泄漏风险,如未清除的定时器、事件监听器、闭包引用",
|
|
70
|
+
"关注不必要的重复计算、冗余 I/O 操作、频繁的序列化/反序列化",
|
|
71
|
+
"检查数据库查询性能,如 N+1 查询、缺少索引提示、未使用批量操作",
|
|
72
|
+
"关注前端渲染性能,如不必要的重渲染、大列表未虚拟化、未使用 memo/useMemo/useCallback",
|
|
73
|
+
"检查网络请求优化,如缺少缓存策略、未合并请求、未使用分页/懒加载",
|
|
74
|
+
"关注并发与异步性能,如未充分利用并行、串行等待可并行的 Promise",
|
|
75
|
+
"检查资源加载优化,如大文件未压缩、图片未优化、未使用 CDN 或懒加载",
|
|
76
|
+
"关注数据结构选择是否合理,如频繁查找时应使用 Map/Set 而非数组遍历",
|
|
77
|
+
];
|
|
78
|
+
/** 完整文件内容的最大字符数,超过则截断以控制 token 消耗 */
|
|
79
|
+
const MAX_FILE_CONTENT_CHARS = 50000;
|
|
80
|
+
/**
|
|
81
|
+
* 尝试从磁盘读取完整文件内容
|
|
82
|
+
* CI 环境下工作目录即 git 仓库根目录,文件路径与 diff 中一致
|
|
83
|
+
*/
|
|
84
|
+
const tryReadFileContent = (filePath) => {
|
|
85
|
+
try {
|
|
86
|
+
const resolved = path.resolve(filePath);
|
|
87
|
+
if (!fs.existsSync(resolved))
|
|
88
|
+
return null;
|
|
89
|
+
const content = fs.readFileSync(resolved, "utf-8");
|
|
90
|
+
if (content.length > MAX_FILE_CONTENT_CHARS) {
|
|
91
|
+
console.error(`[performance-reviewer] ${filePath} 文件过大 (${content.length} chars),截断至 ${MAX_FILE_CONTENT_CHARS}`);
|
|
92
|
+
return content.slice(0, MAX_FILE_CONTENT_CHARS) + "\n// ... 文件内容过长,已截断 ...";
|
|
93
|
+
}
|
|
94
|
+
return content;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const buildSystemPrompt = () => {
|
|
101
|
+
return "你是一个资深的代码性能优化专家,需要根据给定的完整文件内容和 Git diff,对改动的代码进行严格的性能审查。你只关注性能相关的问题,忽略纯代码风格和命名规范问题。完整文件内容用于帮助你理解上下文(如调用关系、循环嵌套、数据流等),但你只能对 diff 中新增/修改的行发表评论。";
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 构建单文件性能审查的用户 Prompt
|
|
105
|
+
* @param fileContent 完整文件内容(可选,用于提供上下文)
|
|
106
|
+
*/
|
|
107
|
+
const buildUserPrompt = (filePath, fileDiff, rules, fileContent) => {
|
|
108
|
+
const rulesBrief = rules.join("\n");
|
|
109
|
+
const addedLinesIndex = (0, utils_1.buildAddedLinesIndexText)(fileDiff, 5000);
|
|
110
|
+
const parts = [
|
|
111
|
+
`请你针对文件 ${filePath} 的 diff 中【新增或修改】的代码行给出性能相关的审查意见。`,
|
|
112
|
+
"仅关注性能问题,不要评论代码风格、命名规范等非性能相关的问题。",
|
|
113
|
+
"下面提供了该文件的完整内容(用于理解上下文,如调用关系、循环嵌套、数据流等)和 Git diff(标识了哪些行是新增/修改的)。",
|
|
114
|
+
"你只能对 diff 中的【新增行(+)】发表评论,但需要结合完整文件内容来判断性能影响。",
|
|
115
|
+
"",
|
|
116
|
+
"输出格式要求:必须返回严格的 JSON,形如:",
|
|
117
|
+
"{",
|
|
118
|
+
' "comments": [',
|
|
119
|
+
" {",
|
|
120
|
+
` "file": "${filePath}",`,
|
|
121
|
+
' "line": 123,',
|
|
122
|
+
' "code": "该行的完整代码内容(不含 +/- 前缀)",',
|
|
123
|
+
' "severity": "info",',
|
|
124
|
+
' "message": "性能问题描述(中文,简洁具体,说明性能影响)",',
|
|
125
|
+
' "suggestion": "优化建议或更高效的写法(中文)"',
|
|
126
|
+
" }",
|
|
127
|
+
" ]",
|
|
128
|
+
"}",
|
|
129
|
+
"注意:",
|
|
130
|
+
"1. 一定要保证输出是合法的 JSON,不能包含注释或多余文本;",
|
|
131
|
+
'2. 如果没有任何性能问题,也请返回 {"comments": []};',
|
|
132
|
+
"3. severity 只能是 info / warning / error 之一;",
|
|
133
|
+
" - info:轻微的性能优化建议(如可选的 memo 优化)",
|
|
134
|
+
" - warning:较明显的性能问题(如 N+1 查询、不必要的重复计算)",
|
|
135
|
+
" - error:严重的性能缺陷(如内存泄漏、死循环风险、O(n²) 可优化为 O(n))",
|
|
136
|
+
`4. file 必须使用 ${filePath};`,
|
|
137
|
+
"5. line 必须是 new file 侧的真实行号(也就是 @@ ... +<newStart> ... 计算出来的行号);",
|
|
138
|
+
"6. code 必须与该 file:line 对应的“目标行内容”一致(不包含 diff 的 +/- 前缀);",
|
|
139
|
+
"7. 你只能对【新增行(+)】发表评论;如果 line 不对应新增行,请改选最近的新增行。",
|
|
140
|
+
"",
|
|
141
|
+
"可评论行索引(仅 diff 中的新增行 +,用于精确选择 line 与 code):",
|
|
142
|
+
addedLinesIndex,
|
|
143
|
+
"",
|
|
144
|
+
"性能审查规则:",
|
|
145
|
+
rulesBrief,
|
|
146
|
+
];
|
|
147
|
+
// 完整文件内容(优先提供,便于模型理解上下文)
|
|
148
|
+
if (fileContent) {
|
|
149
|
+
parts.push("", `下面是 ${filePath} 的完整文件内容(带行号,仅供上下文参考):`, fileContent
|
|
150
|
+
.split("\n")
|
|
151
|
+
.map((line, i) => `${i + 1}| ${line}`)
|
|
152
|
+
.join("\n"));
|
|
153
|
+
}
|
|
154
|
+
// Git diff(标识变更行)
|
|
155
|
+
parts.push("", "下面是 Git diff(标识了哪些行是新增/修改的):", fileDiff);
|
|
156
|
+
return parts.join("\n");
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* 对单个文件执行性能审查
|
|
160
|
+
*/
|
|
161
|
+
const reviewSingleFile = async (filePath, fileDiff, config, rules) => {
|
|
162
|
+
console.error(`[performance-reviewer] 审查文件: ${filePath}`);
|
|
163
|
+
// 尝试读取完整文件内容,为模型提供上下文(调用关系、循环嵌套等)
|
|
164
|
+
const fileContent = tryReadFileContent(filePath);
|
|
165
|
+
if (fileContent) {
|
|
166
|
+
console.error(`[performance-reviewer] ${filePath} 已读取完整文件内容 (${fileContent.length} chars)`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.error(`[performance-reviewer] ${filePath} 无法读取完整文件,仅使用 diff`);
|
|
170
|
+
}
|
|
171
|
+
const systemPrompt = buildSystemPrompt();
|
|
172
|
+
const userPrompt = buildUserPrompt(filePath, fileDiff, rules, fileContent);
|
|
173
|
+
// 创建适配器并调用 AI API
|
|
174
|
+
const modelAdapter = (0, factory_1.createModelAdapterFactory)(config.ai.endpoint, config.ai.apiKey);
|
|
175
|
+
const rawContent = await modelAdapter.call(config.ai.model, systemPrompt, userPrompt);
|
|
176
|
+
if (!rawContent) {
|
|
177
|
+
console.error(`[performance-reviewer] ${filePath} 未获得 AI 响应,跳过`);
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
// 解析响应
|
|
181
|
+
const result = (0, parseResponse_1.parseAIResponse)(rawContent);
|
|
182
|
+
if (!result) {
|
|
183
|
+
console.error(`[performance-reviewer] ${filePath} AI 响应解析失败,跳过`);
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
let comments = result.comments || [];
|
|
187
|
+
// 关键:把 AI 的 file/line 纠偏到 diff 中真实存在的新增行,避免错贴到无关代码
|
|
188
|
+
comments = (0, utils_1.alignCommentsToDiffAddedLines)(fileDiff, comments);
|
|
189
|
+
console.error(`[performance-reviewer] ${filePath} 审查完成,发现 ${comments.length} 条性能问题`);
|
|
190
|
+
return comments;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* 执行 AI 性能审查(按单文件调用模型,仅审查 js/ts/tsx/jsx/vue 文件)
|
|
194
|
+
*/
|
|
195
|
+
const reviewPerformance = async (diff, config) => {
|
|
196
|
+
// 先根据 ignore 规则过滤掉不需要审查的文件 diff
|
|
197
|
+
const effectiveDiff = (0, utils_1.filterDiffByIgnore)(diff, config.review?.ignore);
|
|
198
|
+
if (!effectiveDiff || !effectiveDiff.trim()) {
|
|
199
|
+
console.error("[performance-reviewer] diff 为空,跳过审查");
|
|
200
|
+
return { comments: [] };
|
|
201
|
+
}
|
|
202
|
+
if (!config.ai.apiKey || !config.ai.endpoint) {
|
|
203
|
+
console.error("[performance-reviewer] 缺少 API 配置");
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
// 获取规则:性能规则 + 自定义规则
|
|
207
|
+
const rules = [...performanceRules, ...(config.rules?.custom || [])];
|
|
208
|
+
// 将 diff 按文件拆分
|
|
209
|
+
const fileChunks = (0, utils_1.splitDiffByFile)(effectiveDiff);
|
|
210
|
+
// 过滤:仅保留 js/ts/tsx/jsx/vue 文件
|
|
211
|
+
const reviewableChunks = fileChunks.filter((chunk) => isReviewableFile(chunk.file));
|
|
212
|
+
const skippedFiles = fileChunks
|
|
213
|
+
.filter((chunk) => !isReviewableFile(chunk.file))
|
|
214
|
+
.map((chunk) => chunk.file);
|
|
215
|
+
if (skippedFiles.length > 0) {
|
|
216
|
+
console.error("[performance-reviewer] 跳过非 js/ts/vue 文件:", skippedFiles.join(", "));
|
|
217
|
+
}
|
|
218
|
+
if (reviewableChunks.length === 0) {
|
|
219
|
+
console.error("[performance-reviewer] 无符合条件的文件,跳过审查");
|
|
220
|
+
return { comments: [] };
|
|
221
|
+
}
|
|
222
|
+
console.error(`[performance-reviewer] 待审查文件 (${reviewableChunks.length}):`, reviewableChunks.map((c) => c.file).join(", "));
|
|
223
|
+
// 逐文件调用模型审查,收集所有评论
|
|
224
|
+
let allComments = [];
|
|
225
|
+
for (const chunk of reviewableChunks) {
|
|
226
|
+
try {
|
|
227
|
+
const comments = await reviewSingleFile(chunk.file, chunk.diff, config, rules);
|
|
228
|
+
allComments = allComments.concat(comments);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
// 单个文件失败不影响其他文件的审查
|
|
232
|
+
console.error(`[performance-reviewer] ${chunk.file} 审查异常,跳过:`, error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// 过滤 severity
|
|
236
|
+
if (config.review?.severity) {
|
|
237
|
+
const severityOrder = { info: 0, warning: 1, error: 2 };
|
|
238
|
+
const minLevel = severityOrder[config.review.severity];
|
|
239
|
+
allComments = allComments.filter((c) => severityOrder[c.severity] >= minLevel);
|
|
240
|
+
}
|
|
241
|
+
// 限制评论数量
|
|
242
|
+
if (config.review?.maxComments &&
|
|
243
|
+
allComments.length > config.review.maxComments) {
|
|
244
|
+
allComments = allComments.slice(0, config.review.maxComments);
|
|
245
|
+
console.warn(`[performance-reviewer] 评论数量超过限制,仅保留前 ${config.review.maxComments} 条`);
|
|
246
|
+
}
|
|
247
|
+
console.error(`[performance-reviewer] 全部审查完成,共 ${allComments.length} 条性能问题`);
|
|
248
|
+
return { comments: allComments };
|
|
249
|
+
};
|
|
250
|
+
exports.reviewPerformance = reviewPerformance;
|
|
251
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/performanceReviewer/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAE7B,6CAK2B;AAC3B,+DAA8E;AAC9E,6DAA4D;AAE5D,oBAAoB;AACpB,MAAM,6BAA6B,GAAG;IACpC,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC;AAEF;;GAEG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAW,EAAE;IACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,6BAA6B,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG;IACvB,yCAAyC;IACzC,kCAAkC;IAClC,kCAAkC;IAClC,mCAAmC;IACnC,wDAAwD;IACxD,kCAAkC;IAClC,qCAAqC;IACrC,qCAAqC;IACrC,uCAAuC;CACxC,CAAC;AAEF,qCAAqC;AACrC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAErC;;;GAGG;AACH,MAAM,kBAAkB,GAAG,CAAC,QAAgB,EAAiB,EAAE;IAC7D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,OAAO,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;YAC5C,OAAO,CAAC,KAAK,CACX,0BAA0B,QAAQ,UAAU,OAAO,CAAC,MAAM,eAAe,sBAAsB,EAAE,CAClG,CAAC;YACF,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,GAAG,yBAAyB,CAAC;QAC9E,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,GAAW,EAAE;IACrC,OAAO,+IAA+I,CAAC;AACzJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,eAAe,GAAG,CACtB,QAAgB,EAChB,QAAgB,EAChB,KAAe,EACf,WAA0B,EAClB,EAAE;IACV,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,eAAe,GAAG,IAAA,gCAAwB,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAEjE,MAAM,KAAK,GAAa;QACtB,UAAU,QAAQ,kCAAkC;QACpD,iCAAiC;QACjC,kEAAkE;QAClE,8CAA8C;QAC9C,EAAE;QACF,yBAAyB;QACzB,GAAG;QACH,iBAAiB;QACjB,OAAO;QACP,kBAAkB,QAAQ,IAAI;QAC9B,oBAAoB;QACpB,uCAAuC;QACvC,2BAA2B;QAC3B,4CAA4C;QAC5C,uCAAuC;QACvC,OAAO;QACP,KAAK;QACL,GAAG;QACH,KAAK;QACL,kCAAkC;QAClC,sCAAsC;QACtC,4CAA4C;QAC5C,mCAAmC;QACnC,0CAA0C;QAC1C,iDAAiD;QACjD,gBAAgB,QAAQ,GAAG;QAC3B,kEAAkE;QAClE,yDAAyD;QACzD,+CAA+C;QAC/C,EAAE;QACF,4CAA4C;QAC5C,eAAe;QACf,EAAE;QACF,SAAS;QACT,UAAU;KACX,CAAC;IAEF,yBAAyB;IACzB,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CACR,EAAE,EACF,OAAO,QAAQ,wBAAwB,EACvC,WAAW;aACR,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;aACrC,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;IACJ,CAAC;IAED,kBAAkB;IAClB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,8BAA8B,EAAE,QAAQ,CAAC,CAAC;IAEzD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,gBAAgB,GAAG,KAAK,EAC5B,QAAgB,EAChB,QAAgB,EAChB,MAAwB,EACxB,KAAe,EACO,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;IAE1D,kCAAkC;IAClC,MAAM,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,eAAe,WAAW,CAAC,MAAM,SAAS,CAAC,CAAC;IAC9F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,oBAAoB,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,YAAY,GAAG,iBAAiB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAE3E,kBAAkB;IAClB,MAAM,YAAY,GAAG,IAAA,mCAAyB,EAC5C,MAAM,CAAC,EAAE,CAAC,QAAQ,EAClB,MAAM,CAAC,EAAE,CAAC,MAAM,CACjB,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,CACxC,MAAM,CAAC,EAAE,CAAC,KAAK,EACf,YAAY,EACZ,UAAU,CACX,CAAC;IAEF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,eAAe,CAAC,CAAC;QACjE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO;IACP,MAAM,MAAM,GAAG,IAAA,+BAAe,EAAC,UAAU,CAAC,CAAC;IAE3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,0BAA0B,QAAQ,eAAe,CAAC,CAAC;QACjE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IAErC,mDAAmD;IACnD,QAAQ,GAAG,IAAA,qCAA6B,EAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE7D,OAAO,CAAC,KAAK,CACX,0BAA0B,QAAQ,YAAY,QAAQ,CAAC,MAAM,QAAQ,CACtE,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF;;GAEG;AACI,MAAM,iBAAiB,GAAG,KAAK,EACpC,IAAY,EACZ,MAAwB,EACqB,EAAE;IAC/C,gCAAgC;IAChC,MAAM,aAAa,GAAG,IAAA,0BAAkB,EAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACrD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oBAAoB;IACpB,MAAM,KAAK,GAAG,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC;IAErE,eAAe;IACf,MAAM,UAAU,GAAG,IAAA,uBAAe,EAAC,aAAa,CAAC,CAAC;IAElD,8BAA8B;IAC9B,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CACnD,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAC7B,CAAC;IAEF,MAAM,YAAY,GAAG,UAAU;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAChD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CACX,0CAA0C,EAC1C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CACxB,CAAC;IACJ,CAAC;IAED,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACtD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,KAAK,CACX,iCAAiC,gBAAgB,CAAC,MAAM,IAAI,EAC5D,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC/C,CAAC;IAEF,mBAAmB;IACnB,IAAI,WAAW,GAAgB,EAAE,CAAC;IAElC,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CACrC,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,EACV,MAAM,EACN,KAAK,CACN,CAAC;YACF,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,mBAAmB;YACnB,OAAO,CAAC,KAAK,CACX,0BAA0B,KAAK,CAAC,IAAI,WAAW,EAC/C,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC;IAED,cAAc;IACd,IAAI,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC5B,MAAM,aAAa,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvD,WAAW,GAAG,WAAW,CAAC,MAAM,CAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAC7C,CAAC;IACJ,CAAC;IAED,SAAS;IACT,IACE,MAAM,CAAC,MAAM,EAAE,WAAW;QAC1B,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAC9C,CAAC;QACD,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CACV,wCAAwC,MAAM,CAAC,MAAM,CAAC,WAAW,IAAI,CACtE,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,KAAK,CACX,mCAAmC,WAAW,CAAC,MAAM,QAAQ,CAC9D,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC,CAAC;AA9FW,QAAA,iBAAiB,qBA8F5B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Code Reviewer 核心逻辑
|
|
3
|
+
*/
|
|
4
|
+
import { CodeReviewConfig } from "../types";
|
|
5
|
+
import { parseAIResponse } from "./parseResponse";
|
|
6
|
+
/**
|
|
7
|
+
* 执行 AI Code Review
|
|
8
|
+
*/
|
|
9
|
+
export declare const reviewCode: (diff: string, config: CodeReviewConfig) => Promise<ReturnType<typeof parseAIResponse>>;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reviewer/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAO5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AA8DlD;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,QAAQ,gBAAgB,KACvB,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAmE5C,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI Code Reviewer 核心逻辑
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reviewCode = void 0;
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
const factory_1 = require("./modelAdapters/factory");
|
|
9
|
+
const parseResponse_1 = require("./parseResponse");
|
|
10
|
+
/**
|
|
11
|
+
* 通用代码质量规则(始终启用)
|
|
12
|
+
*/
|
|
13
|
+
const commonRules = [
|
|
14
|
+
"重点关注潜在 bug、性能问题、可读性与安全性(如 XSS、注入、敏感信息泄露)",
|
|
15
|
+
"代码要有适当的错误处理",
|
|
16
|
+
"避免代码重复,注意抽离复用逻辑",
|
|
17
|
+
"函数和变量命名要语义化,见名知意",
|
|
18
|
+
"复杂逻辑要添加注释说明",
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* 构建系统 Prompt
|
|
22
|
+
*/
|
|
23
|
+
const buildSystemPrompt = () => {
|
|
24
|
+
return "你是一个资深的代码审查助手,需要根据给定的 Git diff 和项目规范,对改动的代码进行严格的 Code Review。";
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 构建用户 Prompt
|
|
28
|
+
*/
|
|
29
|
+
const buildUserPrompt = (diff, rules) => {
|
|
30
|
+
const rulesBrief = rules.join("\n");
|
|
31
|
+
const addedLinesIndex = (0, utils_1.buildAddedLinesIndexText)(diff, 5000);
|
|
32
|
+
return [
|
|
33
|
+
"请你只针对 diff 中【新增或修改】的代码行给出审查意见。",
|
|
34
|
+
"输出格式要求:必须返回严格的 JSON,形如:",
|
|
35
|
+
"{",
|
|
36
|
+
' "comments": [',
|
|
37
|
+
" {",
|
|
38
|
+
' "file": "src/xxx.tsx",',
|
|
39
|
+
' "line": 123,',
|
|
40
|
+
' "code": "该行的完整代码内容(不含 +/- 前缀)",',
|
|
41
|
+
' "severity": "info",',
|
|
42
|
+
' "message": "问题描述(中文,简洁具体)",',
|
|
43
|
+
' "suggestion": "可选的修复建议或更佳写法(中文)"',
|
|
44
|
+
" }",
|
|
45
|
+
" ]",
|
|
46
|
+
"}",
|
|
47
|
+
"注意:",
|
|
48
|
+
"1. 一定要保证输出是合法的 JSON,不能包含注释或多余文本;",
|
|
49
|
+
'2. 如果没有任何问题,也请返回 {"comments": []};',
|
|
50
|
+
"3. severity 只能是 info / warning / error 之一;",
|
|
51
|
+
"4. file 必须使用 diff 中出现的文件路径(不要带 a/ 或 b/ 前缀);",
|
|
52
|
+
"5. line 必须是 new file 侧的真实行号(也就是 @@ ... +<newStart> ... 计算出来的行号);",
|
|
53
|
+
"6. code 必须与该 file:line 对应的“目标行内容”一致(不包含 diff 的 +/- 前缀);",
|
|
54
|
+
"7. 你只能对【新增行(+)】发表评论;如果 line 不对应新增行,请改选最近的新增行。",
|
|
55
|
+
"",
|
|
56
|
+
"可评论行索引(仅 diff 中的新增行 +,用于精确选择 line 与 code):",
|
|
57
|
+
addedLinesIndex,
|
|
58
|
+
"",
|
|
59
|
+
"项目规范(摘要):",
|
|
60
|
+
rulesBrief,
|
|
61
|
+
"",
|
|
62
|
+
"下面是 Git diff:",
|
|
63
|
+
diff,
|
|
64
|
+
].join("\n");
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* 执行 AI Code Review
|
|
68
|
+
*/
|
|
69
|
+
const reviewCode = async (diff, config) => {
|
|
70
|
+
// 先根据 ignore 规则过滤掉不需要审查的文件 diff
|
|
71
|
+
const effectiveDiff = (0, utils_1.filterDiffByIgnore)(diff, config.review?.ignore);
|
|
72
|
+
if (!effectiveDiff || !effectiveDiff.trim()) {
|
|
73
|
+
console.error("[reviewer] diff 为空,跳过审查");
|
|
74
|
+
return { comments: [] };
|
|
75
|
+
}
|
|
76
|
+
if (!config.ai.apiKey || !config.ai.endpoint) {
|
|
77
|
+
console.error("[reviewer] 缺少 API 配置");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// 获取规则:通用规则 + 自定义规则
|
|
81
|
+
const rules = [...commonRules, ...(config.rules?.custom || [])];
|
|
82
|
+
// 构建 prompt
|
|
83
|
+
const systemPrompt = buildSystemPrompt();
|
|
84
|
+
const userPrompt = buildUserPrompt(effectiveDiff, rules);
|
|
85
|
+
// 创建适配器并调用 AI API
|
|
86
|
+
const modelAdapter = (0, factory_1.createModelAdapterFactory)(config.ai.endpoint, config.ai.apiKey);
|
|
87
|
+
const rawContent = await modelAdapter.call(config.ai.model, systemPrompt, userPrompt);
|
|
88
|
+
if (!rawContent) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// 解析响应
|
|
92
|
+
const result = (0, parseResponse_1.parseAIResponse)(rawContent);
|
|
93
|
+
if (!result) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// 应用过滤和限制
|
|
97
|
+
let comments = result.comments || [];
|
|
98
|
+
// 关键:把 AI 的 file/line 纠偏到 diff 中真实存在的新增行,避免错贴到无关代码
|
|
99
|
+
comments = (0, utils_1.alignCommentsToDiffAddedLines)(effectiveDiff, comments);
|
|
100
|
+
// 过滤 severity
|
|
101
|
+
if (config.review?.severity) {
|
|
102
|
+
const severityOrder = { info: 0, warning: 1, error: 2 };
|
|
103
|
+
const minLevel = severityOrder[config.review.severity];
|
|
104
|
+
comments = comments.filter((c) => severityOrder[c.severity] >= minLevel);
|
|
105
|
+
}
|
|
106
|
+
// 限制评论数量
|
|
107
|
+
if (config.review?.maxComments &&
|
|
108
|
+
comments.length > config.review.maxComments) {
|
|
109
|
+
comments = comments.slice(0, config.review.maxComments);
|
|
110
|
+
console.warn(`[reviewer] 评论数量超过限制,仅保留前 ${config.review.maxComments} 条`);
|
|
111
|
+
}
|
|
112
|
+
return { comments };
|
|
113
|
+
};
|
|
114
|
+
exports.reviewCode = reviewCode;
|
|
115
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reviewer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型适配器基础接口
|
|
3
|
+
*/
|
|
4
|
+
export interface ModelAdapter {
|
|
5
|
+
/**
|
|
6
|
+
* 调用模型进行代码审查
|
|
7
|
+
* @param model 模型名称
|
|
8
|
+
* @param systemPrompt 系统提示词
|
|
9
|
+
* @param userPrompt 用户提示词
|
|
10
|
+
* @returns AI 返回的原始内容,失败返回 null
|
|
11
|
+
*/
|
|
12
|
+
call(model: string, systemPrompt: string, userPrompt: string): Promise<string | null>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,IAAI,CACF,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/base.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型适配器工厂
|
|
3
|
+
* 根据 endpoint 自动选择合适的适配器
|
|
4
|
+
*/
|
|
5
|
+
import { ModelAdapter } from "./base";
|
|
6
|
+
/**
|
|
7
|
+
* 创建模型适配器
|
|
8
|
+
* @param endpoint API 端点
|
|
9
|
+
* @param apiKey API 密钥
|
|
10
|
+
* @returns 对应的模型适配器实例
|
|
11
|
+
*/
|
|
12
|
+
export declare function createModelAdapterFactory(endpoint: string, apiKey: string): ModelAdapter;
|
|
13
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAItC;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,YAAY,CAWd"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createModelAdapterFactory = createModelAdapterFactory;
|
|
4
|
+
const openai_1 = require("./openai");
|
|
5
|
+
const gemini_1 = require("./gemini");
|
|
6
|
+
/**
|
|
7
|
+
* 创建模型适配器
|
|
8
|
+
* @param endpoint API 端点
|
|
9
|
+
* @param apiKey API 密钥
|
|
10
|
+
* @returns 对应的模型适配器实例
|
|
11
|
+
*/
|
|
12
|
+
function createModelAdapterFactory(endpoint, apiKey) {
|
|
13
|
+
// 判断是否为 Gemini
|
|
14
|
+
if (endpoint.includes("generativelanguage.googleapis.com") ||
|
|
15
|
+
endpoint.includes(":generateContent")) {
|
|
16
|
+
return new gemini_1.GeminiAdapter(endpoint, apiKey);
|
|
17
|
+
}
|
|
18
|
+
// 默认使用 OpenAI 适配器(支持 DeepSeek/Qwen/OpenAI 等兼容模型)
|
|
19
|
+
return new openai_1.OpenAIAdapter(endpoint, apiKey);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/factory.ts"],"names":[],"mappings":";;AAcA,8DAcC;AAvBD,qCAAyC;AACzC,qCAAyC;AAEzC;;;;;GAKG;AACH,SAAgB,yBAAyB,CACvC,QAAgB,EAChB,MAAc;IAEd,eAAe;IACf,IACE,QAAQ,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QACtD,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EACrC,CAAC;QACD,OAAO,IAAI,sBAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,iDAAiD;IACjD,OAAO,IAAI,sBAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Gemini 模型适配器
|
|
3
|
+
*/
|
|
4
|
+
import { ModelAdapter } from "./base";
|
|
5
|
+
export declare class GeminiAdapter implements ModelAdapter {
|
|
6
|
+
private endpoint;
|
|
7
|
+
private apiKey;
|
|
8
|
+
constructor(endpoint: string, apiKey: string);
|
|
9
|
+
call(model: string, systemPrompt: string, userPrompt: string): Promise<string | null>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/gemini.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAatC,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKtC,IAAI,CACR,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CA0D1B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GeminiAdapter = void 0;
|
|
4
|
+
class GeminiAdapter {
|
|
5
|
+
constructor(endpoint, apiKey) {
|
|
6
|
+
this.endpoint = endpoint;
|
|
7
|
+
this.apiKey = apiKey;
|
|
8
|
+
}
|
|
9
|
+
async call(model, systemPrompt, userPrompt) {
|
|
10
|
+
try {
|
|
11
|
+
let url = this.endpoint;
|
|
12
|
+
// Gemini: API Key 在 Query 参数中
|
|
13
|
+
// 如果 endpoint 本身不含 ?key=... 则追加
|
|
14
|
+
if (!url.includes("key=")) {
|
|
15
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
16
|
+
url = `${url}${separator}key=${this.apiKey}`;
|
|
17
|
+
}
|
|
18
|
+
// Gemini 的 Payload 结构
|
|
19
|
+
const payload = {
|
|
20
|
+
contents: [
|
|
21
|
+
{
|
|
22
|
+
parts: [
|
|
23
|
+
{
|
|
24
|
+
text: `${systemPrompt}\n\n${userPrompt}`, // Gemini 没有显式的 system role,通常合并到 prompt
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
generationConfig: {
|
|
30
|
+
response_mime_type: "application/json", // Gemini 1.5 Pro/Flash 支持指定 JSON 输出
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(payload),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
console.error(`[GeminiAdapter] AI API 调用失败,status=${response.status}`);
|
|
42
|
+
const errorText = await response.text();
|
|
43
|
+
console.error(`[GeminiAdapter] 错误详情: ${errorText}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const resJson = (await response.json());
|
|
47
|
+
const rawContent = resJson.candidates?.[0]?.content?.parts?.[0]?.text;
|
|
48
|
+
if (!rawContent) {
|
|
49
|
+
console.error("[GeminiAdapter] AI 返回内容为空");
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return String(rawContent).trim();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error("[GeminiAdapter] AI API 调用异常:", error);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.GeminiAdapter = GeminiAdapter;
|
|
61
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/gemini.ts"],"names":[],"mappings":";;;AAgBA,MAAa,aAAa;IAIxB,YAAY,QAAgB,EAAE,MAAc;QAC1C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CACR,KAAa,EACb,YAAoB,EACpB,UAAkB;QAElB,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;YAExB,8BAA8B;YAC9B,gCAAgC;YAChC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBAChD,GAAG,GAAG,GAAG,GAAG,GAAG,SAAS,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/C,CAAC;YAED,sBAAsB;YACtB,MAAM,OAAO,GAAG;gBACd,QAAQ,EAAE;oBACR;wBACE,KAAK,EAAE;4BACL;gCACE,IAAI,EAAE,GAAG,YAAY,OAAO,UAAU,EAAE,EAAE,wCAAwC;6BACnF;yBACF;qBACF;iBACF;gBACD,gBAAgB,EAAE;oBAChB,kBAAkB,EAAE,kBAAkB,EAAE,oCAAoC;iBAC7E;aACF,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC9B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,KAAK,CACX,sCAAsC,QAAQ,CAAC,MAAM,EAAE,CACxD,CAAC;gBACF,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACxC,OAAO,CAAC,KAAK,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;gBACpD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;YAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;YAEtE,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBAC3C,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAvED,sCAuEC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI 兼容模型适配器
|
|
3
|
+
* 支持 OpenAI、DeepSeek、Qwen 等使用 OpenAI 兼容接口的模型
|
|
4
|
+
*/
|
|
5
|
+
import { ModelAdapter } from "./base";
|
|
6
|
+
export declare class OpenAIAdapter implements ModelAdapter {
|
|
7
|
+
private endpoint;
|
|
8
|
+
private apiKey;
|
|
9
|
+
constructor(endpoint: string, apiKey: string);
|
|
10
|
+
call(model: string, systemPrompt: string, userPrompt: string): Promise<string | null>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/openai.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAWtC,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKtC,IAAI,CACR,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAmD1B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIAdapter = void 0;
|
|
4
|
+
class OpenAIAdapter {
|
|
5
|
+
constructor(endpoint, apiKey) {
|
|
6
|
+
this.endpoint = endpoint;
|
|
7
|
+
this.apiKey = apiKey;
|
|
8
|
+
}
|
|
9
|
+
async call(model, systemPrompt, userPrompt) {
|
|
10
|
+
try {
|
|
11
|
+
let url = this.endpoint;
|
|
12
|
+
// 如果 endpoint 不以 /chat/completions 结尾,自动补全
|
|
13
|
+
if (!url.endsWith("/chat/completions")) {
|
|
14
|
+
url = url.replace(/\/+$/, ""); // 移除末尾斜杠
|
|
15
|
+
url += "/chat/completions";
|
|
16
|
+
}
|
|
17
|
+
const payload = {
|
|
18
|
+
model,
|
|
19
|
+
messages: [
|
|
20
|
+
{ role: "system", content: systemPrompt },
|
|
21
|
+
{ role: "user", content: userPrompt },
|
|
22
|
+
],
|
|
23
|
+
stream: false,
|
|
24
|
+
};
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(payload),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
console.error(`[OpenAIAdapter] AI API 调用失败,status=${response.status}`);
|
|
35
|
+
const errorText = await response.text();
|
|
36
|
+
console.error(`[OpenAIAdapter] 错误详情: ${errorText}`);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const resJson = (await response.json());
|
|
40
|
+
const rawContent = resJson.choices?.[0]?.message?.content;
|
|
41
|
+
if (!rawContent) {
|
|
42
|
+
console.error("[OpenAIAdapter] AI 返回内容为空");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return String(rawContent).trim();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error("[OpenAIAdapter] AI API 调用异常:", error);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.OpenAIAdapter = OpenAIAdapter;
|
|
54
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/reviewer/modelAdapters/openai.ts"],"names":[],"mappings":";;;AAeA,MAAa,aAAa;IAIxB,YAAY,QAAgB,EAAE,MAAc;QAC1C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CACR,KAAa,EACb,YAAoB,EACpB,UAAkB;QAElB,IAAI,CAAC;YACH,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;YAExB,2CAA2C;YAC3C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBACvC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;gBACxC,GAAG,IAAI,mBAAmB,CAAC;YAC7B,CAAC;YAED,MAAM,OAAO,GAAG;gBACd,KAAK;gBACL,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;oBACzC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE;iBACtC;gBACD,MAAM,EAAE,KAAK;aACd,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;iBACvC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;aAC9B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,KAAK,CACX,sCAAsC,QAAQ,CAAC,MAAM,EAAE,CACxD,CAAC;gBACF,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACxC,OAAO,CAAC,KAAK,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;gBACpD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;YAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;YAE1D,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBAC3C,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAhED,sCAgEC"}
|