Skip to content

Commit af250fd

Browse files
🔧 chore(features): 添加代码评审功能
- 【核心功能】新增代码评审命令和配置项 - 【基础设施】实现代码评审prompt和结果展示 - 【功能增强】扩展AI Provider支持代码评审能力 - 【工具支持】新增代码评审命令集成界面 - 【文档完善】补充各类型定义和注释说明
1 parent e09e3bc commit af250fd

9 files changed

+885
-25
lines changed

package.json

+26
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@
6868
"category": "[Dish AI Commit]",
6969
"icon": "/images/icon.svg",
7070
"description": "使用 AI 生成周报"
71+
},
72+
{
73+
"command": "dish-ai-commit.reviewCode",
74+
"title": "Review Code with AI",
75+
"category": "[Dish AI Commit]",
76+
"icon": "/images/icon.svg",
77+
"description": "使用 AI 对代码进行评审"
7178
}
7279
],
7380
"configuration": {
@@ -209,6 +216,11 @@
209216
"type": "string",
210217
"default": "",
211218
"description": "Custom system prompt"
219+
},
220+
"dish-ai-commit.features.codeReview.systemPrompt": {
221+
"type": "string",
222+
"default": "Custom system prompt",
223+
"description": "Custom system prompt for code review"
212224
}
213225
}
214226
},
@@ -225,13 +237,23 @@
225237
"command": "extension.dish-ai-commit",
226238
"when": "((config.svn.enabled && scmProvider == svn) || (config.git.enabled && scmProvider == git)) && scmResourceGroup != unversioned && scmResourceGroup != external && scmResourceGroup != conflicts && scmResourceGroup != remotechanges",
227239
"group": "1_modification"
240+
},
241+
{
242+
"command": "dish-ai-commit.reviewCode",
243+
"when": "((config.svn.enabled && scmProvider == svn) || (config.git.enabled && scmProvider == git)) && scmResourceGroup != unversioned && scmResourceGroup != external && scmResourceGroup != conflicts && scmResourceGroup != remotechanges",
244+
"group": "1_modification"
228245
}
229246
],
230247
"scm/resourceFolder/context": [
231248
{
232249
"command": "extension.dish-ai-commit",
233250
"when": "scmProvider =~ /(git|svn)/",
234251
"group": "inline"
252+
},
253+
{
254+
"command": "dish-ai-commit.reviewCode",
255+
"when": "scmProvider =~ /(git|svn)/",
256+
"group": "inline"
235257
}
236258
],
237259
"commandPalette": [
@@ -247,6 +269,10 @@
247269
{
248270
"command": "dish-ai-commit.generateWeeklyReport",
249271
"when": "true"
272+
},
273+
{
274+
"command": "dish-ai-commit.reviewCode",
275+
"when": "((config.svn.enabled && svnOpenRepositoryCount > 0) || (config.git.enabled && gitOpenRepositoryCount > 0))"
250276
}
251277
]
252278
}

src/ai/providers/BaseOpenAIProvider.ts

+147-6
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,57 @@
11
import OpenAI from "openai";
22
import { ChatCompletionMessageParam } from "openai/resources";
3-
import { AIProvider, AIRequestParams, AIResponse, AIModel } from "../types";
4-
import { generateWithRetry, getSystemPrompt } from "../utils/generateHelper";
3+
import {
4+
AIProvider,
5+
AIRequestParams,
6+
AIResponse,
7+
AIModel,
8+
type CodeReviewResult,
9+
} from "../types";
10+
import {
11+
generateWithRetry,
12+
getCodeReviewPrompt,
13+
getSystemPrompt,
14+
} from "../utils/generateHelper";
515
import { LocalizationManager } from "../../utils/LocalizationManager";
616
import { getWeeklyReportPrompt } from "../../prompt/weeklyReport";
17+
import { CodeReviewReportGenerator } from "../../utils/CodeReviewReportGenerator";
718

19+
/**
20+
* OpenAI提供者配置项接口
21+
*/
822
export interface OpenAIProviderConfig {
23+
/** OpenAI API密钥 */
924
apiKey: string;
25+
/** API基础URL,对于非官方OpenAI端点可自定义 */
1026
baseURL?: string;
27+
/** API版本号 */
1128
apiVersion?: string;
29+
/** 提供者唯一标识符 */
1230
providerId: string;
31+
/** 提供者显示名称 */
1332
providerName: string;
33+
/** 默认使用的模型ID */
1434
defaultModel?: string;
35+
/** 支持的模型列表 */
1536
models: AIModel[];
1637
}
1738

39+
/**
40+
* OpenAI API基础提供者抽象类
41+
* 实现了OpenAI API的基本功能,可被具体提供者继承和扩展
42+
*/
1843
export abstract class BaseOpenAIProvider implements AIProvider {
44+
/** OpenAI API客户端实例 */
1945
protected openai: OpenAI;
46+
/** 提供者配置信息 */
2047
protected config: OpenAIProviderConfig;
48+
/** 提供者标识信息 */
2149
protected provider: { id: string; name: string };
2250

51+
/**
52+
* 创建基础OpenAI提供者实例
53+
* @param config - 提供者配置对象
54+
*/
2355
constructor(config: OpenAIProviderConfig) {
2456
this.config = config;
2557
this.provider = {
@@ -29,6 +61,11 @@ export abstract class BaseOpenAIProvider implements AIProvider {
2961
this.openai = this.createClient();
3062
}
3163

64+
/**
65+
* 创建OpenAI API客户端
66+
* @returns OpenAI客户端实例
67+
* @protected
68+
*/
3269
protected createClient(): OpenAI {
3370
const config: any = {
3471
apiKey: this.config.apiKey,
@@ -41,11 +78,17 @@ export abstract class BaseOpenAIProvider implements AIProvider {
4178
config.defaultHeaders = { "api-key": this.config.apiKey };
4279
}
4380
}
44-
console.log("config", config);
4581

4682
return new OpenAI(config);
4783
}
4884

85+
/**
86+
* 生成AI回复内容
87+
* 使用重试机制处理可能的失败情况
88+
*
89+
* @param params - AI请求参数
90+
* @returns 包含生成内容和使用统计的Promise
91+
*/
4992
async generateResponse(params: AIRequestParams): Promise<AIResponse> {
5093
return generateWithRetry(
5194
params,
@@ -85,12 +128,92 @@ export abstract class BaseOpenAIProvider implements AIProvider {
85128
);
86129
}
87130

131+
/**
132+
* 生成代码评审报告
133+
* 将diff内容转换为结构化的评审结果
134+
*
135+
* @param params - 代码评审请求参数
136+
* @returns 包含评审报告的Promise
137+
* @throws 如果AI响应解析失败或生成过程出错
138+
*/
139+
async generateCodeReview(params: AIRequestParams): Promise<AIResponse> {
140+
return generateWithRetry(
141+
params,
142+
async (truncatedInput) => {
143+
const messages: ChatCompletionMessageParam[] = [
144+
{
145+
role: "system",
146+
content: getCodeReviewPrompt(params),
147+
},
148+
{
149+
role: "user",
150+
content: truncatedInput,
151+
},
152+
];
153+
154+
try {
155+
const completion = await this.openai.chat.completions.create({
156+
model:
157+
(params.model && params.model.id) ||
158+
this.config.defaultModel ||
159+
"gpt-3.5-turbo",
160+
messages,
161+
temperature: 0.3,
162+
});
163+
164+
const responseContent = completion.choices[0]?.message?.content;
165+
if (!responseContent) {
166+
throw new Error("No response content from AI");
167+
}
168+
169+
let reviewResult: CodeReviewResult;
170+
try {
171+
reviewResult = JSON.parse(responseContent);
172+
} catch (e) {
173+
throw new Error(
174+
`Failed to parse AI response as JSON: ${
175+
e instanceof Error ? e.message : String(e)
176+
}`
177+
);
178+
}
179+
return {
180+
content:
181+
CodeReviewReportGenerator.generateMarkdownReport(reviewResult),
182+
usage: {
183+
promptTokens: completion.usage?.prompt_tokens,
184+
completionTokens: completion.usage?.completion_tokens,
185+
totalTokens: completion.usage?.total_tokens,
186+
},
187+
};
188+
} catch (error) {
189+
const message = LocalizationManager.getInstance().format(
190+
"codeReview.generation.failed",
191+
error instanceof Error ? error.message : String(error)
192+
);
193+
throw new Error(message);
194+
}
195+
},
196+
{
197+
initialMaxLength: params.model?.maxTokens?.input || 16385,
198+
provider: this.getId(),
199+
}
200+
);
201+
}
202+
203+
/**
204+
* 基于提交记录生成周报
205+
* 总结一段时间内的代码提交活动
206+
*
207+
* @param commits - 提交记录数组
208+
* @param model - 可选的指定模型
209+
* @returns 包含周报内容的Promise
210+
* @throws 如果生成失败会抛出本地化的错误信息
211+
*/
88212
async generateWeeklyReport(
89213
commits: string[],
90214
model?: AIModel
91215
): Promise<AIResponse> {
92216
try {
93-
console.log("commits", commits);
94217
const response = await this.openai.chat.completions.create({
95218
model: model?.id || this.config.defaultModel || "gpt-3.5-turbo",
96219
messages: [
@@ -104,7 +227,6 @@ export abstract class BaseOpenAIProvider implements AIProvider {
104227
},
105228
],
106229
});
107-
console.log("response", response);
108230
return {
109231
content: response.choices[0]?.message?.content || "",
110232
usage: {
@@ -123,11 +245,16 @@ export abstract class BaseOpenAIProvider implements AIProvider {
123245
}
124246
}
125247

248+
/**
249+
* 获取当前支持的AI模型列表
250+
* 优先从API获取,如果失败则返回配置的静态列表
251+
*
252+
* @returns Promise<AIModel[]> 支持的模型配置数组
253+
*/
126254
async getModels(): Promise<AIModel[] | any[]> {
127255
try {
128256
const response = await this.openai.models.list();
129257
return response.data.map((model: any) => {
130-
console.log("model", model);
131258
return {
132259
id: model.id,
133260
name: model.id,
@@ -144,18 +271,32 @@ export abstract class BaseOpenAIProvider implements AIProvider {
144271
}
145272
}
146273

274+
/**
275+
* 刷新并返回可用的模型ID列表
276+
* @returns Promise<string[]> 模型ID数组
277+
*/
147278
async refreshModels(): Promise<string[]> {
148279
const models = await this.getModels();
149280
return models.map((m) => m.id);
150281
}
151282

283+
/**
284+
* 获取提供者显示名称
285+
*/
152286
getName(): string {
153287
return this.provider.name;
154288
}
155289

290+
/**
291+
* 获取提供者唯一标识符
292+
*/
156293
getId(): string {
157294
return this.provider.id;
158295
}
159296

297+
/**
298+
* 检查服务是否可用的抽象方法
299+
* 需要由具体提供者实现
300+
*/
160301
abstract isAvailable(): Promise<boolean>;
161302
}

0 commit comments

Comments
 (0)