Skip to content

Commit 04e999e

Browse files
✨ feat(weeklyReport): 添加生成周报的功能
- 在 AIProvider 接口中添加 generateWeeklyReport 方法 - 实现 generateWeeklyReport 方法在 OpenAI、Ollama 和 VSCode 提供商中 - 更新 GenerateWeeklyReportCommand 命令,集成生成周报功能 - 修改周报面板,展示生成的周报内容 - 调整相关文件以支持周报生成
1 parent b557c7a commit 04e999e

11 files changed

+228
-50
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@
259259
},
260260
{
261261
"command": "dish-ai-commit.generateWeeklyReport",
262-
"when": ""
262+
"when": "true"
263263
}
264264
]
265265
}

src/ai/AIProviderFactory.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class AIProviderFactory {
3434
let provider = this.providers.get(providerType);
3535
console.log("AIProvider", AIProvider);
3636
console.log("providerType", providerType.toLowerCase());
37-
console.log("AIProvider.VSCODE", AIProvider.VSCODE);
37+
console.log("AIProvider.VSCODE", AIProvider.ZHIPU);
3838
if (!provider) {
3939
switch (providerType.toLowerCase()) {
4040
case AIProvider.OPENAI:
@@ -46,7 +46,7 @@ export class AIProviderFactory {
4646
case AIProvider.VS_CODE_PROVIDED:
4747
provider = new VSCodeProvider();
4848
break;
49-
case AIProvider.ZHIPU:
49+
case AIProvider.ZHIPUAI:
5050
provider = new ZhipuAIProvider();
5151
break;
5252
case AIProvider.DASHSCOPE:

src/ai/providers/BaseOpenAIProvider.ts

+37
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import OpenAI from "openai";
22
import { ChatCompletionMessageParam } from "openai/resources";
33
import { AIProvider, AIRequestParams, AIResponse, AIModel } from "../types";
44
import { generateWithRetry, getSystemPrompt } from "../utils/generateHelper";
5+
import { LocalizationManager } from "../../utils/LocalizationManager";
56

67
export interface OpenAIProviderConfig {
78
apiKey: string;
@@ -83,6 +84,42 @@ export abstract class BaseOpenAIProvider implements AIProvider {
8384
);
8485
}
8586

87+
async generateWeeklyReport(commits: string[]): Promise<AIResponse> {
88+
try {
89+
const messages: ChatCompletionMessageParam[] = [
90+
{
91+
role: "system",
92+
content: "请根据以下commit生成一份周报:",
93+
},
94+
{
95+
role: "user",
96+
content: commits.join("\n"),
97+
},
98+
];
99+
100+
const completion = await this.openai.chat.completions.create({
101+
model: this.config.defaultModel || "gpt-3.5-turbo",
102+
messages,
103+
});
104+
105+
return {
106+
content: completion.choices[0]?.message?.content || "",
107+
usage: {
108+
promptTokens: completion.usage?.prompt_tokens,
109+
completionTokens: completion.usage?.completion_tokens,
110+
totalTokens: completion.usage?.total_tokens,
111+
},
112+
};
113+
} catch (error) {
114+
throw new Error(
115+
LocalizationManager.getInstance().format(
116+
"weeklyReport.generation.failed",
117+
error instanceof Error ? error.message : String(error)
118+
)
119+
);
120+
}
121+
}
122+
86123
async getModels(): Promise<AIModel[]> {
87124
return Promise.resolve(this.config.models);
88125
}

src/ai/providers/OllamaProvider.ts

+34
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,40 @@ export class OllamaProvider implements AIProvider {
9696
);
9797
}
9898

99+
async generateWeeklyReport(commits: string[]): Promise<AIResponse> {
100+
const model = this.configManager.getConfig("BASE_MODEL");
101+
102+
const response = await this.ollama.chat({
103+
model: (model as any).id,
104+
messages: [
105+
{
106+
role: "system",
107+
content: "请根据以下commit生成一份周报:",
108+
},
109+
{
110+
role: "user",
111+
content: commits.join("\n"),
112+
},
113+
],
114+
stream: false,
115+
});
116+
117+
let content = "";
118+
try {
119+
const jsonContent = JSON.parse(response.message.content);
120+
content = jsonContent.response || response.message.content;
121+
} catch {
122+
content = response.message.content;
123+
}
124+
125+
return {
126+
content,
127+
usage: {
128+
totalTokens: response.total_duration,
129+
},
130+
};
131+
}
132+
99133
async isAvailable(): Promise<boolean> {
100134
try {
101135
await this.ollama.list();

src/ai/providers/VscodeProvider.ts

+36
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ export class VSCodeProvider implements AIProvider {
107107
}
108108
}
109109

110+
async generateWeeklyReport(commits: string[]): Promise<AIResponse> {
111+
try {
112+
const models = await vscode.lm.selectChatModels();
113+
if (!models || models.length === 0) {
114+
throw new Error(
115+
LocalizationManager.getInstance().getMessage(
116+
"vscode.no.models.available"
117+
)
118+
);
119+
}
120+
121+
const chatModel = models[0];
122+
const messages = [
123+
vscode.LanguageModelChatMessage.User(
124+
`请根据以下commit生成一份周报:\n${commits.join("\n")}`
125+
),
126+
];
127+
128+
const response = await chatModel.sendRequest(messages);
129+
130+
let result = "";
131+
for await (const fragment of response.text) {
132+
result += fragment;
133+
}
134+
135+
return { content: result.trim() };
136+
} catch (error) {
137+
throw new Error(
138+
LocalizationManager.getInstance().format(
139+
"weeklyReport.generation.failed",
140+
error instanceof Error ? error.message : String(error)
141+
)
142+
);
143+
}
144+
}
145+
110146
async refreshModels(): Promise<any> {
111147
// VSCode的模型是动态的,不需要刷新
112148
return Promise.resolve();

src/ai/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export interface AIModel<
6969

7070
export interface AIProvider {
7171
generateResponse(params: AIRequestParams): Promise<AIResponse>;
72+
generateWeeklyReport(commits: string[]): Promise<AIResponse>;
7273
isAvailable(): Promise<boolean>;
7374
refreshModels(): Promise<string[]>;
7475
getModels(): Promise<AIModel[]>; // 更新返回类型

src/commands.ts

-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export class CommandManager implements vscode.Disposable {
1717
const generateCommand = new GenerateCommitCommand(this.context);
1818
const selectModelCommand = new SelectModelCommand(this.context);
1919
const weeklyReportCommand = new GenerateWeeklyReportCommand(this.context);
20-
console.log("COMMANDS.MODEL.SHOW", COMMANDS.MODEL.SHOW);
2120

2221
this.disposables.push(
2322
vscode.commands.registerCommand(
@@ -37,7 +36,6 @@ export class CommandManager implements vscode.Disposable {
3736
try {
3837
await selectModelCommand.execute();
3938
} catch (error) {
40-
console.log("error", error);
4139
NotificationHandler.error(
4240
"command.select.model.failed",
4341
error instanceof Error ? error.message : String(error)

src/commands/GenerateWeeklyReportCommand.ts

+74-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { ProgressHandler } from "../utils/ProgressHandler";
55
import { LocalizationManager } from "../utils/LocalizationManager";
66
import { WeeklyReportPanel } from "../webview/WeeklyReportPanel";
77
import { SCMFactory } from "../scm/SCMProvider";
8+
import { AIProviderFactory } from "../ai/AIProviderFactory";
9+
import { exec } from "child_process";
10+
import { ConfigurationManager } from "../config/ConfigurationManager";
811

912
export class GenerateWeeklyReportCommand extends BaseCommand {
1013
async validateConfig(): Promise<boolean> {
@@ -19,16 +22,63 @@ export class GenerateWeeklyReportCommand extends BaseCommand {
1922
return true;
2023
}
2124

25+
async getPeriod(): Promise<string | undefined> {
26+
const options = ["本周", "上一周", "上两周"];
27+
const selection = await vscode.window.showQuickPick(options, {
28+
placeHolder: "选择一个时间段",
29+
});
30+
31+
switch (selection) {
32+
case "本周":
33+
return "1 week ago";
34+
case "上一周":
35+
return "2 weeks ago";
36+
case "上两周":
37+
return "3 weeks ago";
38+
default:
39+
return undefined;
40+
}
41+
}
42+
2243
async execute(): Promise<void> {
2344
try {
24-
if (!(await this.validateConfig())) {
45+
const period = await this.getPeriod();
46+
if (!period) {
2547
return;
2648
}
2749

2850
await ProgressHandler.withProgress(
2951
LocalizationManager.getInstance().getMessage("weeklyReport.generating"),
3052
async () => {
31-
WeeklyReportPanel.createOrShow(this.context.extensionUri);
53+
const scmProvider = await SCMFactory.detectSCM();
54+
if (!scmProvider) {
55+
await NotificationHandler.error(
56+
LocalizationManager.getInstance().getMessage("scm.not.detected")
57+
);
58+
return;
59+
}
60+
61+
const config = ConfigurationManager.getInstance();
62+
const configuration = config.getConfiguration();
63+
64+
// 检查是否已配置 AI 提供商和模型
65+
let provider = configuration.base.provider;
66+
let model = configuration.base.model;
67+
68+
const commits = await this.getCommits(period);
69+
const aiProvider = AIProviderFactory.getProvider("ZHIPUAI");
70+
const response = await aiProvider.generateWeeklyReport(commits);
71+
72+
if (response?.content) {
73+
vscode.window.showInformationMessage("周报生成成功");
74+
WeeklyReportPanel.createOrShow(this.context.extensionUri);
75+
WeeklyReportPanel.currentPanel?._panel.webview.postMessage({
76+
command: "report",
77+
data: response.content,
78+
});
79+
} else {
80+
vscode.window.showErrorMessage("周报生成失败");
81+
}
3282
}
3383
);
3484
} catch (error) {
@@ -42,4 +92,26 @@ export class GenerateWeeklyReportCommand extends BaseCommand {
4292
}
4393
}
4494
}
95+
96+
private async getCommits(period: string): Promise<string[]> {
97+
return new Promise((resolve, reject) => {
98+
const workspaceFolders = vscode.workspace.workspaceFolders;
99+
if (!workspaceFolders || workspaceFolders.length === 0) {
100+
return reject("没有打开的工作区");
101+
}
102+
103+
const command = `git log --since="${period}" --pretty=format:"%h - %an, %ar : %s"`;
104+
exec(
105+
command,
106+
{ cwd: workspaceFolders[0].uri.fsPath },
107+
(error, stdout, stderr) => {
108+
if (error) {
109+
reject(`获取commit历史记录失败: ${stderr}`);
110+
} else {
111+
resolve(stdout.split("\n"));
112+
}
113+
}
114+
);
115+
});
116+
}
45117
}

src/constants.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export const COMMANDS = {
1212
SHOW: packageJson.contributes.commands[1].command,
1313
},
1414
WEEKLY_REPORT: {
15-
GENERATE: 'dish-ai-commit.generateWeeklyReport'
16-
}
15+
GENERATE: packageJson.contributes.commands[2].command,
16+
},
1717
} as const;
1818

1919
// 添加类型导出

src/services/weeklyReport.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class WeeklyReportService {
1818

1919
constructor() {}
2020

21-
async generate(): Promise<WorkItem[]> {
21+
async generate(commits: string[]): Promise<WorkItem[]> {
2222
const scmProvider = await SCMFactory.detectSCM();
2323
if (!scmProvider) {
2424
throw new Error("No SCM provider detected");
@@ -30,8 +30,7 @@ export class WeeklyReportService {
3030
throw new Error("Unable to detect author information");
3131
}
3232

33-
const repositories = await this.findRepositories();
34-
await this.collectLogs(repositories, author);
33+
this.allLogs = commits;
3534
return this.processLogs();
3635
}
3736

@@ -93,7 +92,7 @@ export class WeeklyReportService {
9392
private getLastWeekDates(): { start: Date; end: Date } {
9493
const today = new Date();
9594
const currentDay = today.getDay();
96-
95+
9796
// 计算上周一的日期
9897
const lastMonday = new Date(today);
9998
lastMonday.setDate(today.getDate() - currentDay - 7 + 1);
@@ -111,7 +110,7 @@ export class WeeklyReportService {
111110
const { start, end } = this.getLastWeekDates();
112111
const startDate = start.toISOString();
113112
const endDate = end.toISOString();
114-
113+
115114
const command = `git log --after="${startDate}" --before="${endDate}" --author="${author}" --pretty=format:"%s"`;
116115
try {
117116
const { stdout } = await execAsync(command, { cwd: repoPath });

0 commit comments

Comments
 (0)